Skip to content

SecCertificate::public_key_info_der #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 15, 2019
4 changes: 2 additions & 2 deletions security-framework-sys/src/key.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use core_foundation_sys::base::CFTypeID;
#[cfg(target_os = "macos")]
#[cfg(any(target_os = "macos", target_os = "ios"))]
use core_foundation_sys::data::CFDataRef;
use core_foundation_sys::dictionary::CFDictionaryRef;
#[cfg(target_os = "macos")]
#[cfg(any(target_os = "macos", target_os = "ios"))]
use core_foundation_sys::error::CFErrorRef;

use base::SecKeyRef;
Expand Down
173 changes: 173 additions & 0 deletions security-framework/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,37 @@ use core_foundation_sys::base::kCFAllocatorDefault;
use security_framework_sys::base::{errSecParam, SecCertificateRef};
use security_framework_sys::certificate::*;
use std::fmt;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use std::ptr;

use base::{Error, Result};
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use core_foundation_sys::base::{CFComparisonResult, CFRelease};
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use core_foundation_sys::data::{CFDataGetBytePtr, CFDataGetLength};
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use core_foundation_sys::dictionary::CFDictionaryGetValueIfPresent;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use core_foundation_sys::error::CFErrorRef;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use core_foundation_sys::number::{kCFNumberSInt32Type, CFNumberGetValue};
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use core_foundation_sys::string::{CFStringCompareFlags, CFStringRef};
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use cvt;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use security_framework_sys::base::{SecKeyRef, SecPolicyRef};
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use security_framework_sys::item::*;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use security_framework_sys::key::{SecKeyCopyAttributes, SecKeyCopyExternalRepresentation};
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use security_framework_sys::policy::SecPolicyCreateBasicX509;
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
use security_framework_sys::trust::{
SecTrustCopyPublicKey, SecTrustCreateWithCertificates, SecTrustEvaluate, SecTrustRef,
SecTrustResultType,
};

declare_TCFType! {
/// A type representing a certificate.
Expand Down Expand Up @@ -57,8 +86,152 @@ impl SecCertificate {
CFString::wrap_under_create_rule(summary).to_string()
}
}

#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
/// Returns DER encoded subjectPublicKeyInfo of certificate if available. This can be used
/// for certificate pinning.
pub fn public_key_info_der(&self) -> Result<Option<Vec<u8>>> {
Copy link
Owner

Choose a reason for hiding this comment

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

My concern is that this is a very specific single-purpose high-level function. Could it be broken down into components that help others write their public_key_info_der and alike themselves?

// Imported from TrustKit
// https://github.com/datatheorem/TrustKit/blob/master/TrustKit/Pinning/TSKSPKIHashCache.m
unsafe {
let public_key = self.copy_public_key_from_certificate()?;
Copy link
Owner

Choose a reason for hiding this comment

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

Is there a reason why this function is not public?

let mut error: CFErrorRef = ptr::null_mut();
let public_key_attributes = SecKeyCopyAttributes(public_key);
Copy link
Owner

Choose a reason for hiding this comment

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

Maybe there could be a high-level wrapper for SecKeyCopyAttributes, so that it doesn't require so much unsafe boilerplate for each property?


let mut public_key_type: *const std::os::raw::c_void = ptr::null();
let mut have_vals = true;
have_vals = have_vals
&& CFDictionaryGetValueIfPresent(
public_key_attributes,
kSecAttrKeyType as _,
&mut public_key_type as _,
) > 0;
let mut public_keysize: *const std::os::raw::c_void = ptr::null();
have_vals = have_vals
&& CFDictionaryGetValueIfPresent(
public_key_attributes,
kSecAttrKeySizeInBits as _,
&mut public_keysize as *mut *const std::os::raw::c_void,
) > 0;
CFRelease(public_key_attributes as _);
if !have_vals {
CFRelease(public_key as _);
return Ok(None);
}

let mut public_keysize_val: u32 = 0;
let public_keysize_val_ptr: *mut u32 = &mut public_keysize_val;
have_vals = CFNumberGetValue(
public_keysize as _,
kCFNumberSInt32Type,
public_keysize_val_ptr as _,
);
if !have_vals {
CFRelease(public_key as _);
return Ok(None);
}
let hdr_bytes = get_asn1_header_bytes(public_key_type as _, public_keysize_val);
if hdr_bytes.len() == 0 {
CFRelease(public_key as _);
return Ok(None);
}

let public_key_data = SecKeyCopyExternalRepresentation(public_key, &mut error);
if public_key_data == ptr::null() {
CFRelease(public_key as _);
return Ok(None);
}

let key_data_len = CFDataGetLength(public_key_data) as usize;
let key_data_slice = std::slice::from_raw_parts(
CFDataGetBytePtr(public_key_data) as *const u8,
key_data_len,
);
let mut out = Vec::with_capacity(hdr_bytes.len() + key_data_len);
out.extend_from_slice(hdr_bytes);
out.extend_from_slice(key_data_slice);

CFRelease(public_key_data as _);
CFRelease(public_key as _);
Ok(Some(out))
}
}

#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
fn copy_public_key_from_certificate(&self) -> Result<SecKeyRef> {
unsafe {
// Create an X509 trust using the using the certificate
let mut trust: SecTrustRef = ptr::null_mut();
let policy: SecPolicyRef = SecPolicyCreateBasicX509();
cvt(SecTrustCreateWithCertificates(
self.as_concrete_TypeRef() as _,
policy as _,
&mut trust,
))?;

// Get a public key reference for the certificate from the trust
let mut result: SecTrustResultType = 0;
cvt(SecTrustEvaluate(trust, &mut result))?;
let public_key = SecTrustCopyPublicKey(trust);
CFRelease(policy as _);
CFRelease(trust as _);
Ok(public_key)
}
}
}

#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
fn get_asn1_header_bytes(pkt: CFStringRef, ksz: u32) -> &'static [u8] {
unsafe {
if CFStringCompare(pkt, kSecAttrKeyTypeRSA, 0) as i64 == 0 && ksz == 2048 {
return &RSA_2048_ASN1_HEADER;
}
if CFStringCompare(pkt, kSecAttrKeyTypeRSA, 0) as i64 == 0 && ksz == 4096 {
return &RSA_4096_ASN1_HEADER;
}
if CFStringCompare(pkt, kSecAttrKeyTypeECSECPrimeRandom, 0) as i64 == 0 && ksz == 256 {
return &EC_DSA_SECP_256_R1_ASN1_HEADER;
}
if CFStringCompare(pkt, kSecAttrKeyTypeECSECPrimeRandom, 0) as i64 == 0 && ksz == 384 {
return &EC_DSA_SECP_384_R1_ASN1_HEADER;
}
}
&[]
Copy link
Owner

Choose a reason for hiding this comment

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

Better error handling would be to return None

}

extern "C" {
#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
pub fn CFStringCompare(
Copy link
Owner

Choose a reason for hiding this comment

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

You don't need this. CFString implements Eq, so you can compare them with just ==

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm using CFStringRef's due to comparing with constants kSecAttrKeyTypeRSA and kSecAttrKeyTypeECSECPrimeRandom. Can I turn those into CFString without allocating?

Copy link
Owner

Choose a reason for hiding this comment

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

I think you can. The CFString is a refcounted type, so a conversion from Ref to non-Ref should only need to change reference count:

https://docs.rs/core-foundation/0.6.3/core_foundation/base/trait.TCFType.html#tymethod.wrap_under_get_rule

Copy link
Owner

Choose a reason for hiding this comment

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

Also the eq is implemented as self.as_CFType().eq(&other.as_CFType())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok switched to CFString now.

theString1: CFStringRef,
theString2: CFStringRef,
compareOptions: CFStringCompareFlags,
) -> CFComparisonResult;
}

#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
const RSA_2048_ASN1_HEADER: [u8; 24] = [
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
];

#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
const RSA_4096_ASN1_HEADER: [u8; 24] = [
0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00,
];

#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
const EC_DSA_SECP_256_R1_ASN1_HEADER: [u8; 26] = [
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
];

#[cfg(any(feature = "OSX_10_12", target_os = "ios"))]
const EC_DSA_SECP_384_R1_ASN1_HEADER: [u8; 23] = [
0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b,
0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00,
];

#[cfg(test)]
mod test {
use test::certificate;
Expand Down