Merge pull request #85 from vhbit/x509-load-pem
Cert loading from PEM & restructuring
This commit is contained in:
commit
6277635e5c
|
|
@ -142,7 +142,7 @@ impl PKey {
|
||||||
let mut mem_bio = try!(MemBio::new());
|
let mut mem_bio = try!(MemBio::new());
|
||||||
unsafe {
|
unsafe {
|
||||||
try_ssl!(ffi::PEM_write_bio_PrivateKey(mem_bio.get_handle(), self.evp, ptr::null(),
|
try_ssl!(ffi::PEM_write_bio_PrivateKey(mem_bio.get_handle(), self.evp, ptr::null(),
|
||||||
ptr::null_mut(), -1, ptr::null_mut(), ptr::null_mut()));
|
ptr::null_mut(), -1, None, ptr::null_mut()));
|
||||||
|
|
||||||
}
|
}
|
||||||
let buf = try!(mem_bio.read_to_end().map_err(StreamError));
|
let buf = try!(mem_bio.read_to_end().map_err(StreamError));
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ pub type CRYPTO_EX_dup = extern "C" fn(to: *mut CRYPTO_EX_DATA,
|
||||||
pub type CRYPTO_EX_free = extern "C" fn(parent: *mut c_void, ptr: *mut c_void,
|
pub type CRYPTO_EX_free = extern "C" fn(parent: *mut c_void, ptr: *mut c_void,
|
||||||
ad: *mut CRYPTO_EX_DATA, idx: c_int,
|
ad: *mut CRYPTO_EX_DATA, idx: c_int,
|
||||||
argl: c_long, argp: *mut c_void);
|
argl: c_long, argp: *mut c_void);
|
||||||
pub type PrivateKeyWriteCallback = extern "C" fn(buf: *mut c_char, size: c_int,
|
pub type PasswordCallback = extern "C" fn(buf: *mut c_char, size: c_int,
|
||||||
rwflag: c_int, user_data: *mut c_void)
|
rwflag: c_int, user_data: *mut c_void)
|
||||||
-> c_int;
|
-> c_int;
|
||||||
|
|
||||||
|
|
@ -356,9 +356,12 @@ extern "C" {
|
||||||
pub fn HMAC_Final(ctx: *mut HMAC_CTX, output: *mut u8, len: *mut c_uint);
|
pub fn HMAC_Final(ctx: *mut HMAC_CTX, output: *mut u8, len: *mut c_uint);
|
||||||
pub fn HMAC_Update(ctx: *mut HMAC_CTX, input: *const u8, len: c_uint);
|
pub fn HMAC_Update(ctx: *mut HMAC_CTX, input: *const u8, len: c_uint);
|
||||||
|
|
||||||
|
|
||||||
|
pub fn PEM_read_bio_X509(bio: *mut BIO, out: *mut *mut X509, callback: Option<PasswordCallback>,
|
||||||
|
user_data: *mut c_void) -> *mut X509;
|
||||||
pub fn PEM_write_bio_PrivateKey(bio: *mut BIO, pkey: *mut EVP_PKEY, cipher: *const EVP_CIPHER,
|
pub fn PEM_write_bio_PrivateKey(bio: *mut BIO, pkey: *mut EVP_PKEY, cipher: *const EVP_CIPHER,
|
||||||
kstr: *mut c_char, klen: c_int,
|
kstr: *mut c_char, klen: c_int,
|
||||||
callback: *mut c_void,
|
callback: Option<PasswordCallback>,
|
||||||
user_data: *mut c_void) -> c_int;
|
user_data: *mut c_void) -> c_int;
|
||||||
pub fn PEM_write_bio_X509(bio: *mut BIO, x509: *mut X509) -> c_int;
|
pub fn PEM_write_bio_X509(bio: *mut BIO, x509: *mut X509) -> c_int;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::io::{File, Open, Write, Writer};
|
use serialize::hex::FromHex;
|
||||||
|
use std::io::{Writer};
|
||||||
use std::io::net::tcp::TcpStream;
|
use std::io::net::tcp::TcpStream;
|
||||||
use std::num::FromStrRadix;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use crypto::hash::{SHA256};
|
use crypto::hash::{SHA256};
|
||||||
use ssl::{Sslv23, SslContext, SslStream, SslVerifyPeer, SslVerifyNone};
|
use ssl::{Sslv23, SslContext, SslStream, SslVerifyPeer};
|
||||||
use x509::{X509Generator, DigitalSignature, KeyEncipherment, ClientAuth, ServerAuth, X509StoreContext};
|
use x509::{X509StoreContext};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_new_ctx() {
|
fn test_new_ctx() {
|
||||||
|
|
@ -142,19 +142,6 @@ fn test_verify_trusted_get_error_err() {
|
||||||
assert!(SslStream::new(&ctx, stream).is_err());
|
assert!(SslStream::new(&ctx, stream).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_str_to_vec(s: &str) -> Vec<u8> {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
assert!(s.len() % 2 == 0, "Hash str should have len = 2 * n");
|
|
||||||
for i in range(0, s.len() / 2) {
|
|
||||||
let substr = s.slice(i, i + 2);
|
|
||||||
let t: Option<u8> = FromStrRadix::from_str_radix(substr, 16);
|
|
||||||
assert!(t.is_some(), "Hash str must contain only hex digits, i.e. [0-9a-f]");
|
|
||||||
res.push(t.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_verify_callback_data() {
|
fn test_verify_callback_data() {
|
||||||
fn callback(_preverify_ok: bool, x509_ctx: &X509StoreContext, node_id: &Vec<u8>) -> bool {
|
fn callback(_preverify_ok: bool, x509_ctx: &X509StoreContext, node_id: &Vec<u8>) -> bool {
|
||||||
|
|
@ -175,8 +162,8 @@ fn test_verify_callback_data() {
|
||||||
// Command: openssl x509 -in test/cert.pem -outform DER | openssl dgst -sha256
|
// Command: openssl x509 -in test/cert.pem -outform DER | openssl dgst -sha256
|
||||||
// Please update if "test/cert.pem" will ever change
|
// Please update if "test/cert.pem" will ever change
|
||||||
let node_hash_str = "6204f6617e1af7495394250655f43600cd483e2dfc2005e92d0fe439d0723c34";
|
let node_hash_str = "6204f6617e1af7495394250655f43600cd483e2dfc2005e92d0fe439d0723c34";
|
||||||
let node_id = hash_str_to_vec(node_hash_str);
|
let node_id = node_hash_str.from_hex().unwrap();
|
||||||
ctx.set_verify_with_data(SslVerifyNone, callback, node_id);
|
ctx.set_verify_with_data(SslVerifyPeer, callback, node_id);
|
||||||
ctx.set_verify_depth(1);
|
ctx.set_verify_depth(1);
|
||||||
|
|
||||||
match SslStream::new(&ctx, stream) {
|
match SslStream::new(&ctx, stream) {
|
||||||
|
|
@ -205,35 +192,3 @@ fn test_read() {
|
||||||
let buf = stream.read_to_end().ok().expect("read error");
|
let buf = stream.read_to_end().ok().expect("read error");
|
||||||
print!("{}", str::from_utf8(buf.as_slice()));
|
print!("{}", str::from_utf8(buf.as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cert_gen() {
|
|
||||||
let gen = X509Generator::new()
|
|
||||||
.set_bitlength(2048)
|
|
||||||
.set_valid_period(365*2)
|
|
||||||
.set_CN("test_me")
|
|
||||||
.set_sign_hash(SHA256)
|
|
||||||
.set_usage([DigitalSignature, KeyEncipherment])
|
|
||||||
.set_ext_usage([ClientAuth, ServerAuth]);
|
|
||||||
|
|
||||||
let res = gen.generate();
|
|
||||||
assert!(res.is_ok());
|
|
||||||
|
|
||||||
let (cert, pkey) = res.unwrap();
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
static NULL_PATH: &'static str = "/dev/null";
|
|
||||||
#[cfg(windows)]
|
|
||||||
static NULL_PATH: &'static str = "nul";
|
|
||||||
|
|
||||||
let cert_path = Path::new(NULL_PATH);
|
|
||||||
let mut file = File::open_mode(&cert_path, Open, Write).unwrap();
|
|
||||||
assert!(cert.write_pem(&mut file).is_ok());
|
|
||||||
|
|
||||||
let key_path = Path::new(NULL_PATH);
|
|
||||||
let mut file = File::open_mode(&key_path, Open, Write).unwrap();
|
|
||||||
assert!(pkey.write_pem(&mut file).is_ok());
|
|
||||||
|
|
||||||
// FIXME: check data in result to be correct, needs implementation
|
|
||||||
// of X509 getters
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ use ffi;
|
||||||
use ssl::error::{SslError, StreamError};
|
use ssl::error::{SslError, StreamError};
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
pub enum X509FileType {
|
pub enum X509FileType {
|
||||||
PEM = ffi::X509_FILETYPE_PEM,
|
PEM = ffi::X509_FILETYPE_PEM,
|
||||||
|
|
@ -322,6 +325,7 @@ impl X509Generator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// A public key certificate
|
/// A public key certificate
|
||||||
pub struct X509<'ctx> {
|
pub struct X509<'ctx> {
|
||||||
|
|
@ -331,6 +335,39 @@ pub struct X509<'ctx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> X509<'ctx> {
|
impl<'ctx> X509<'ctx> {
|
||||||
|
/// Creates new from handle with desired ownership.
|
||||||
|
pub fn new(handle: *mut ffi::X509, owned: bool) -> X509<'ctx> {
|
||||||
|
X509 {
|
||||||
|
ctx: None,
|
||||||
|
handle: handle,
|
||||||
|
owned: owned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new certificate from context. Doesn't take ownership
|
||||||
|
/// of handle.
|
||||||
|
pub fn new_in_ctx(handle: *mut ffi::X509, ctx: &'ctx X509StoreContext) -> X509<'ctx> {
|
||||||
|
X509 {
|
||||||
|
ctx: Some(ctx),
|
||||||
|
handle: handle,
|
||||||
|
owned: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads certificate from PEM, takes ownership of handle
|
||||||
|
pub fn from_pem(reader: &mut Reader) -> Result<X509<'ctx>, SslError> {
|
||||||
|
let mut mem_bio = try!(MemBio::new());
|
||||||
|
let buf = try!(reader.read_to_end().map_err(StreamError));
|
||||||
|
try!(mem_bio.write(buf.as_slice()).map_err(StreamError));
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let handle = try_ssl_null!(ffi::PEM_read_bio_X509(mem_bio.get_handle(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
None, ptr::null_mut()));
|
||||||
|
Ok(X509::new(handle, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn subject_name<'a>(&'a self) -> X509Name<'a> {
|
pub fn subject_name<'a>(&'a self) -> X509Name<'a> {
|
||||||
let name = unsafe { ffi::X509_get_subject_name(self.handle) };
|
let name = unsafe { ffi::X509_get_subject_name(self.handle) };
|
||||||
X509Name { x509: self, name: name }
|
X509Name { x509: self, name: name }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
use serialize::hex::FromHex;
|
||||||
|
use std::io::{File, Open, Read};
|
||||||
|
use std::io::util::NullWriter;
|
||||||
|
|
||||||
|
use crypto::hash::{SHA256};
|
||||||
|
use x509::{X509, X509Generator, DigitalSignature, KeyEncipherment, ClientAuth, ServerAuth};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cert_gen() {
|
||||||
|
let gen = X509Generator::new()
|
||||||
|
.set_bitlength(2048)
|
||||||
|
.set_valid_period(365*2)
|
||||||
|
.set_CN("test_me")
|
||||||
|
.set_sign_hash(SHA256)
|
||||||
|
.set_usage([DigitalSignature, KeyEncipherment])
|
||||||
|
.set_ext_usage([ClientAuth, ServerAuth]);
|
||||||
|
|
||||||
|
let res = gen.generate();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
|
||||||
|
let (cert, pkey) = res.unwrap();
|
||||||
|
|
||||||
|
let mut writer = NullWriter;
|
||||||
|
assert!(cert.write_pem(&mut writer).is_ok());
|
||||||
|
assert!(pkey.write_pem(&mut writer).is_ok());
|
||||||
|
|
||||||
|
// FIXME: check data in result to be correct, needs implementation
|
||||||
|
// of X509 getters
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cert_loading() {
|
||||||
|
let cert_path = Path::new("test/cert.pem");
|
||||||
|
let mut file = File::open_mode(&cert_path, Open, Read)
|
||||||
|
.ok()
|
||||||
|
.expect("Failed to open `test/cert.pem`");
|
||||||
|
|
||||||
|
let cert = X509::from_pem(&mut file).ok().expect("Failed to load PEM");
|
||||||
|
let fingerprint = cert.fingerprint(SHA256).unwrap();
|
||||||
|
|
||||||
|
// Hash was generated as SHA256 hash of certificate "test/cert.pem"
|
||||||
|
// in DER format.
|
||||||
|
// Command: openssl x509 -in test/cert.pem -outform DER | openssl dgst -sha256
|
||||||
|
// Please update if "test/cert.pem" will ever change
|
||||||
|
let hash_str = "6204f6617e1af7495394250655f43600cd483e2dfc2005e92d0fe439d0723c34";
|
||||||
|
let hash_vec = hash_str.from_hex().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(fingerprint.as_slice(), hash_vec.as_slice());
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue