From eb269b8967f4fa1e4451ea13bca35b624934e687 Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Wed, 10 Jul 2019 10:19:01 -0700 Subject: [PATCH 1/9] Implement PKCS8 certificate support for all three backends. --- Cargo.toml | 5 +- src/imp/openssl.rs | 34 ++++-- src/imp/schannel.rs | 38 +++++- src/imp/security_framework.rs | 30 +++++ src/lib.rs | 11 +- src/pem.rs | 220 ++++++++++++++++++++++++++++++++++ src/test.rs | 49 +++++++- test/chain.pem | 48 ++++++++ test/key.pem | 28 +++++ 9 files changed, 448 insertions(+), 15 deletions(-) create mode 100644 src/pem.rs create mode 100644 test/chain.pem create mode 100644 test/key.pem diff --git a/Cargo.toml b/Cargo.toml index fc43d89c..1ad06b98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,13 @@ readme = "README.md" [features] vendored = ["openssl/vendored"] +[dependencies] +lazy_static = "1.0" +rustc-serialize = "0.3" + [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] security-framework = "0.3.1" security-framework-sys = "0.3.1" -lazy_static = "1.0" libc = "0.2" tempfile = "3.0" diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index cc83f2e6..3556802b 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -5,19 +5,19 @@ use self::openssl::error::ErrorStack; use self::openssl::hash::MessageDigest; use self::openssl::nid::Nid; use self::openssl::pkcs12::Pkcs12; -use self::openssl::pkey::PKey; +use self::openssl::pkey::{PKey, Private}; use self::openssl::ssl::{ self, MidHandshakeSslStream, SslAcceptor, SslConnector, SslContextBuilder, SslMethod, SslVerifyMode, }; -use self::openssl::x509::{X509, X509VerifyResult}; +use self::openssl::x509::{X509VerifyResult, X509}; use std::error; use std::fmt; use std::io; use std::sync::{Once, ONCE_INIT}; +use pem; use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; -use self::openssl::pkey::Private; #[cfg(have_min_max_version)] fn supported_protocols( @@ -155,7 +155,7 @@ impl From for Error { pub struct Identity { pkey: PKey, cert: X509, - chain: Vec, + chain: Option>, } impl Identity { @@ -165,7 +165,19 @@ impl Identity { Ok(Identity { pkey: parsed.pkey, cert: parsed.cert, - chain: parsed.chain.into_iter().flat_map(|x| x).collect(), + chain: parsed.chain.map(|stack| stack.into_iter().collect()), + }) + } + + pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { + let pkey = PKey::private_key_from_pem(key)?; + let p_block = pem::PemBlock::new(buf); + let mut chain: Vec = p_block.map(|buf| X509::from_pem(buf).unwrap()).collect(); + let cert = chain.pop(); + Ok(Identity { + pkey, + cert: cert.expect("need identity cert"), + chain: Some(chain), }) } } @@ -265,8 +277,10 @@ impl TlsConnector { if let Some(ref identity) = builder.identity { connector.set_certificate(&identity.0.cert)?; connector.set_private_key(&identity.0.pkey)?; - for cert in identity.0.chain.iter().rev() { - connector.add_extra_chain_cert(cert.to_owned())?; + if let Some(ref chain) = identity.0.chain { + for cert in chain.iter().rev() { + connector.add_extra_chain_cert(cert.to_owned())?; + } } } supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?; @@ -314,8 +328,10 @@ impl TlsAcceptor { let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; acceptor.set_private_key(&builder.identity.0.pkey)?; acceptor.set_certificate(&builder.identity.0.cert)?; - for cert in builder.identity.0.chain.iter().rev() { - acceptor.add_extra_chain_cert(cert.to_owned())?; + if let Some(ref chain) = builder.identity.0.chain { + for cert in chain.iter().rev() { + acceptor.add_extra_chain_cert(cert.to_owned())?; + } } supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?; diff --git a/src/imp/schannel.rs b/src/imp/schannel.rs index d38e6f37..ec1be964 100644 --- a/src/imp/schannel.rs +++ b/src/imp/schannel.rs @@ -1,8 +1,9 @@ extern crate schannel; -use self::schannel::cert_context::{CertContext, HashAlgorithm}; +use self::schannel::cert_context::{CertContext, HashAlgorithm, KeySpec}; use self::schannel::cert_store::{CertAdd, CertStore, Memory, PfxImportOptions}; use self::schannel::schannel_cred::{Direction, Protocol, SchannelCred}; +use self::schannel::crypt_prov::{AcquireOptions, ProviderType}; use self::schannel::tls_stream; use std::error; use std::fmt; @@ -96,6 +97,41 @@ impl Identity { Ok(Identity { cert: identity }) } + + pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { + let mut store = Memory::new()?.into_store(); + let mut cert_iter = crate::pem::PemBlock::new(pem).into_iter(); + let leaf = cert_iter.next().unwrap(); + let cert = CertContext::from_pem(std::str::from_utf8(leaf).unwrap()).unwrap(); + + let mut options = AcquireOptions::new(); + options.container("schannel"); + let type_ = ProviderType::rsa_full(); + + let mut container = match options.acquire(type_) { + Ok(container) => container, + Err(_) => options.new_keyset(true).acquire(type_).unwrap(), + }; + let key = crate::pem::pem_to_der(key, Some(crate::pem::PEM_PRIVATE_KEY)).unwrap(); + container.import() + .import_pkcs8(&key) + .unwrap(); + + cert.set_key_prov_info() + .container("schannel") + .type_(type_) + .keep_open(true) + .key_spec(KeySpec::key_exchange()) + .set() + .unwrap(); + let mut context = store.add_cert(&cert, CertAdd::Always)?; + + for int_cert in cert_iter { + let certificate = Certificate::from_pem(int_cert)?; + context = store.add_cert(&certificate.0, schannel::cert_store::CertAdd::Always)?; + } + Ok(Identity{cert: context}) + } } #[derive(Clone)] diff --git a/src/imp/security_framework.rs b/src/imp/security_framework.rs index 4008b080..886e1b1b 100644 --- a/src/imp/security_framework.rs +++ b/src/imp/security_framework.rs @@ -23,6 +23,8 @@ use self::security_framework::os::macos::certificate::{PropertyType, SecCertific #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::certificate_oids::CertificateOid; #[cfg(not(target_os = "ios"))] +use self::security_framework::os::macos::identity::SecIdentityExt; +#[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::import_export::{ImportOptions, SecItems}; #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::keychain::{self, KeychainSettings, SecKeychain}; @@ -85,6 +87,34 @@ pub struct Identity { } impl Identity { + pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { + let dir = TempDir::new().unwrap(); + let keychain = keychain::CreateOptions::new() + .password("password") + .create(dir.path().join("identity.keychain")) + .unwrap(); + + let mut items = SecItems::default(); + + ImportOptions::new() + .filename("key.pem") + .items(&mut items) + .keychain(&keychain) + .import(&key)?; + + ImportOptions::new() + .filename("chain.pem") + .items(&mut items) + .keychain(&keychain) + .import(&pem)?; + + let ident = SecIdentity::with_certificate(&[keychain], &items.certificates[0])?; + Ok(Identity { + identity: ident, + chain: items.certificates + }) + } + pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { let mut imports = Identity::import_options(buf, pass)?; let import = imports.pop().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 34f3fa0a..01230d3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,12 +97,14 @@ #![warn(missing_docs)] #[macro_use] -#[cfg(any(target_os = "macos", target_os = "ios"))] extern crate lazy_static; #[cfg(test)] extern crate hex; +extern crate rustc_serialize; +mod pem; + use std::any::Any; use std::error; use std::fmt; @@ -183,6 +185,13 @@ impl Identity { let identity = imp::Identity::from_pkcs12(der, password)?; Ok(Identity(identity)) } + + /// buf is the contents of a file containing a chain of PEM encoded certificates + /// key is the contents of a file containing a PEM encoded private key + pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { + let identity = imp::Identity::from_pkcs8(buf, key)?; + Ok(Identity(identity)) + } } /// An X509 certificate. diff --git a/src/pem.rs b/src/pem.rs new file mode 100644 index 00000000..93e0103b --- /dev/null +++ b/src/pem.rs @@ -0,0 +1,220 @@ +#![allow(unused)] +use rustc_serialize::base64::{self, FromBase64, ToBase64}; + +/// Type of the various `PEM_*` constants supplied to `pem_to_der` / `der_to_pem`. +pub struct PemGuard { + begin: &'static str, + end: &'static str, +} + +macro_rules! pem_guard { + ($n:expr) => { + &PemGuard { + begin: concat!("-----BEGIN ", $n, "-----"), + end: concat!("-----END ", $n, "-----"), + } + } +} + +// Ref. RFC7468, although these are not universally respected. +pub const PEM_CERTIFICATE: &'static PemGuard = pem_guard!("CERTIFICATE"); +pub const PEM_CERTIFICATE_REQUEST: &'static PemGuard = pem_guard!("CERTIFICATE REQUEST"); +pub const PEM_ENCRYPTED_PRIVATE_KEY: &'static PemGuard = pem_guard!("ENCRYPTED PRIVATE KEY"); +pub const PEM_PRIVATE_KEY: &'static PemGuard = pem_guard!("PRIVATE KEY"); +pub const PEM_PUBLIC_KEY: &'static PemGuard = pem_guard!("PUBLIC KEY"); +pub const PEM_CMS: &'static PemGuard = pem_guard!("CMS"); + +const BASE64_PEM_WRAP: usize = 64; + +lazy_static!{ + static ref BASE64_PEM: base64::Config = base64::Config { + char_set: base64::CharacterSet::Standard, + newline: base64::Newline::LF, + pad: true, + line_length: Some(BASE64_PEM_WRAP), + }; +} + +/// Convert PEM to DER. If `guard` is specified (e.g. as PEM_CERTIFICATE), then the guardlines are +/// verified to match the expected string. Otherwise, the guardlines are verified to generally have +/// the correct form. +/// +/// On failure (due to guardlines syntax or an illegal PEM character), returns None. +pub fn pem_to_der>(pem: &T, guard: Option<&PemGuard>) -> Option> { + let pem = match std::str::from_utf8(pem.as_ref()) { + Err(_) => return None, + Ok(p) => p, + }; + let pem = match pem.find("-----") { + Some(i) => pem.split_at(i).1, + None => return None, + }; + let mut lines = pem.lines(); + + let begin = match lines.next() { + Some(l) => l, + None => return None, + }; + let end = match lines.last() { + Some(l) => l, + None => return None, + }; + + if let Some(g) = guard { + if begin != g.begin || end != g.end { + return None; + } + } else { + if !begin.starts_with("-----BEGIN ") || !begin.ends_with("-----") || + !end.starts_with("-----END") || !end.ends_with("-----") { + return None; + } + } + + let body_start = pem.char_indices() + .skip(begin.len()) + .skip_while(|t| t.1.is_whitespace()) + .next().unwrap().0; + let body_end = pem.rmatch_indices(&end).next().unwrap().0; + pem[body_start..body_end].from_base64().ok() +} + +/// Convert DER to PEM. The guardlines use the identifying string chosen by `guard` +/// (e.g. PEM_CERTIFICATE). +pub fn der_to_pem>(der: &T, guard: &PemGuard) -> String { + let mut pem = String::new(); + + pem.push_str(guard.begin); + pem.push('\n'); + if der.as_ref().len() > 0 { + pem.push_str(&der.as_ref().to_base64(*BASE64_PEM)); + pem.push('\n'); + } + pem.push_str(guard.end); + pem.push('\n'); + + pem +} + +/// Split data by PEM guard lines +pub struct PemBlock<'a> { + pem_block: &'a str, + cur_end: usize, +} + +impl<'a> PemBlock<'a> { + pub fn new(data: &'a [u8]) -> PemBlock<'a> { + let s = ::std::str::from_utf8(data).unwrap(); + PemBlock { + pem_block: s, + cur_end: s.find("-----BEGIN").unwrap_or(s.len()), + } + } +} + +impl<'a> Iterator for PemBlock<'a> { + type Item = &'a [u8]; + fn next(&mut self) -> Option { + let last = self.pem_block.len(); + if self.cur_end >= last { + return None; + } + let begin = self.cur_end; + let pos = self.pem_block[begin + 1..].find("-----BEGIN"); + self.cur_end = match pos { + Some(end) => end + begin + 1, + None => last, + }; + return Some(&self.pem_block[begin..self.cur_end].as_bytes()); + } +} + +#[test] +fn test_pem() { + assert!(pem_to_der("", None).is_none()); + assert!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----END JUNK-----\n", Some(PEM_CERTIFICATE)).is_none()); + assert!(pem_to_der("-----BEGIN JUNK-----\n-----END CERTIFICATE-----\n", Some(PEM_CERTIFICATE)).is_none()); + assert_eq!(pem_to_der("-----BEGIN JUNK-----\n-----END GARBAGE-----\n", None).unwrap(), vec![]); + assert_eq!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n", None).unwrap(), vec![]); + assert!(pem_to_der("-----EGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n", None).is_none()); + assert!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----ND CERTIFICATE-----\n", None).is_none()); + assert!(pem_to_der("-----BEGIN CERTIFICATE----\n-----END CERTIFICATE-----\n", None).is_none()); + assert!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE----\n", None).is_none()); + assert_eq!(pem_to_der("-----BEGIN JUNK-----\n\ + AAECAwQFBgcICQoLDA0ODw==\n\ + -----END GARBAGE-----\n", None).unwrap(), + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + assert_eq!(pem_to_der("-----BEGIN CERTIFICATE-----\n\ + AAECAwQFBgcICQoLDA0ODw==\n\ + -----END CERTIFICATE-----\n", None).unwrap(), + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); +} + +#[test] +fn test_roundtrip_whole_line() { + // Test the case where the PEM is a multiple of whole lines. + let test_cert = +"-----BEGIN CERTIFICATE----- +MIIHBTCCBe2gAwIBAgIRAIFsdIAf8kR29DFR7K4znoIwDQYJKoZIhvcNAQELBQAw +-----END CERTIFICATE----- +"; + + assert_eq!(der_to_pem(&pem_to_der(test_cert, Some(PEM_CERTIFICATE)).unwrap(), PEM_CERTIFICATE), test_cert); +} + +#[test] +fn test_wrapping() { + let mut v: Vec = vec![]; + let bytes_per_line = BASE64_PEM_WRAP * 3 / 4; + for i in 0..2*bytes_per_line { + let pem = der_to_pem(&v, PEM_CERTIFICATE); + // Check that we can do a round trip, and that we got the expected number of lines. + assert_eq!(pem_to_der(&pem, Some(PEM_CERTIFICATE)).unwrap(), v); + assert_eq!(pem.matches("\n").count(), 2 + (i + bytes_per_line - 1) / bytes_per_line); + v.push(0); + } +} + +#[test] +fn test_split() { + // Split three certs, CRLF line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ + -----BEGIN SECOND-----\r\n-----END SECOND\r\n\ + -----BEGIN THIRD-----\r\n-----END THIRD\r\n").collect::>(), + vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], + b"-----BEGIN SECOND-----\r\n-----END SECOND\r\n", + b"-----BEGIN THIRD-----\r\n-----END THIRD\r\n"]); + // Split three certs, CRLF line terminators except at EOF. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ + -----BEGIN SECOND-----\r\n-----END SECOND-----\r\n\ + -----BEGIN THIRD-----\r\n-----END THIRD-----").collect::>(), + vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], + b"-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n", + b"-----BEGIN THIRD-----\r\n-----END THIRD-----"]); + // Split two certs, LF line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ + -----BEGIN SECOND-----\n-----END SECOND\n").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], + b"-----BEGIN SECOND-----\n-----END SECOND\n"]); + // Split two certs, CR line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r-----END FIRST-----\r\ + -----BEGIN SECOND-----\r-----END SECOND\r").collect::>(), + vec![b"-----BEGIN FIRST-----\r-----END FIRST-----\r" as &[u8], + b"-----BEGIN SECOND-----\r-----END SECOND\r"]); + // Split two certs, LF line terminators except at EOF. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ + -----BEGIN SECOND-----\n-----END SECOND").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], + b"-----BEGIN SECOND-----\n-----END SECOND"]); + // Split a single cert, LF line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8]]); + // Split a single cert, LF line terminators except at EOF. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----" as &[u8]]); + // (Don't) split garbage. + assert_eq!(PemBlock::new(b"junk").collect::>(), + Vec::<&[u8]>::new()); + assert_eq!(PemBlock::new(b"junk-----BEGIN garbage").collect::>(), + vec![b"-----BEGIN garbage" as &[u8]]); +} diff --git a/src/test.rs b/src/test.rs index f52d7fa7..15b68b9c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -286,9 +286,13 @@ mod tests { #[test] fn import_same_identity_multiple_times() { - let buf = include_bytes!("../test/identity.p12"); - let _ = p!(Identity::from_pkcs12(buf, "mypass")); - let _ = p!(Identity::from_pkcs12(buf, "mypass")); + let p12buf = include_bytes!("../test/identity.p12"); + let p8buf = include_bytes!("../test/chain.pem"); + let key = include_bytes!("../test/key.pem"); + let _ = p!(Identity::from_pkcs12(p12buf, "mypass")); + let _ = p!(Identity::from_pkcs12(p12buf, "mypass")); + let _ = p!(Identity::from_pkcs8(p8buf, key)); + let _ = p!(Identity::from_pkcs8(p8buf, key)); } #[test] @@ -372,4 +376,43 @@ mod tests { p!(j.join()); } + + #[test] + fn server_pkcs8() { + let key = include_bytes!("../test/key.pem"); + let cert = include_bytes!("../test/cert.pem"); + + let ident = Identity::from_pkcs8(cert, key).unwrap(); + let builder = p!(TlsAcceptor::new(ident)); + + let listener = p!(TcpListener::bind("0.0.0.0:0")); + let port = p!(listener.local_addr()).port(); + + let j = thread::spawn(move || { + let socket = p!(listener.accept()).0; + let mut socket = p!(builder.accept(socket)); + + let mut buf = [0; 5]; + p!(socket.read_exact(&mut buf)); + assert_eq!(&buf, b"hello"); + + p!(socket.write_all(b"world")); + }); + + let root_ca = include_bytes!("../test/root-ca.der"); + let root_ca = Certificate::from_der(root_ca).unwrap(); + + let socket = p!(TcpStream::connect(("localhost", port))); + let mut builder = TlsConnector::builder(); + builder.add_root_certificate(root_ca); + let builder = p!(builder.build()); + let mut socket = p!(builder.connect("foobar.com", socket)); + + p!(socket.write_all(b"hello")); + let mut buf = vec![]; + p!(socket.read_to_end(&mut buf)); + assert_eq!(buf, b"world"); + + p!(j.join()); + } } diff --git a/test/chain.pem b/test/chain.pem new file mode 100644 index 00000000..6b171293 --- /dev/null +++ b/test/chain.pem @@ -0,0 +1,48 @@ +Bag Attributes + friendlyName: foobar.com + localKeyID: 59 17 2D 93 13 E8 44 59 BC FF 27 F9 67 E7 9E 6E 92 17 E5 84 +subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=foobar.com +issuer=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd +-----BEGIN CERTIFICATE----- +MIIDGzCCAgMCCQCHcfe97pgvpTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE2MDgxNDE3MDAwM1oXDTI2MDgxMjE3MDAwM1owWjELMAkG +A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKZm9vYmFyLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKj0JYxEsxejUIX+I5GH0Hg2G0kX/y1H0+Ub +3mw2/Ja5BD/yN96/7zMSumXF8uS3SkmpyiJkbyD01TSRTqjlP7/VCBlyUIChlpLQ +mrGaijZiT/VCyPXqmcwFzXS5IOTpX1olJfW8rA41U1LCIcDUyFf6LtZ/v8rSeKr6 +TuE6SGV4WRaBm1SrjWBeHVV866CRrtSS1ieT2asFsAyOZqWhk2fakwwBDFWDhOGI +ubfO+5aq9cBJbNRlzsgB3UZs3gC0O6GzbnZ6oT0TiJMeTsXXjABLUlaq/rrqFF4Y +euZkkbHTFBMz288PUc3m3ZTcpN+E7+ZOUBRZXKD20K07NugqCzUCAwEAATANBgkq +hkiG9w0BAQsFAAOCAQEASvYHuIl5C0NHBELPpVHNuLbQsDQNKVj3a54+9q1JkiMM +6taEJYfw7K1Xjm4RoiFSHpQBh+PWZS3hToToL2Zx8JfMR5MuAirdPAy1Sia/J/qE +wQdJccqmvuLkLTSlsGbEJ/LUUgOAgrgHOZM5lUgIhCneA0/dWJ3PsN0zvn69/faY +oo1iiolWiIHWWBUSdr3jM2AJaVAsTmLh00cKaDNk37JB940xConBGSl98JPrNrf9 +dUAiT0iIBngDBdHnn/yTj+InVEFyZSKrNtiDSObFHxPcxGteHNrCPJdP1e+GqkHp +HJMRZVCQpSMzvHlofHSNgzWV1MX5h1CP4SGZdBDTfA== +-----END CERTIFICATE----- +Bag Attributes: +subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd +issuer=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G +ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV +eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr +7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92 +aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc +klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN +XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn +BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv +Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3 +AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy +OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3 +mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9 +GA== +-----END CERTIFICATE----- diff --git a/test/key.pem b/test/key.pem new file mode 100644 index 00000000..d381795d --- /dev/null +++ b/test/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCo9CWMRLMXo1CF +/iORh9B4NhtJF/8tR9PlG95sNvyWuQQ/8jfev+8zErplxfLkt0pJqcoiZG8g9NU0 +kU6o5T+/1QgZclCAoZaS0Jqxmoo2Yk/1Qsj16pnMBc10uSDk6V9aJSX1vKwONVNS +wiHA1MhX+i7Wf7/K0niq+k7hOkhleFkWgZtUq41gXh1VfOugka7UktYnk9mrBbAM +jmaloZNn2pMMAQxVg4ThiLm3zvuWqvXASWzUZc7IAd1GbN4AtDuhs252eqE9E4iT +Hk7F14wAS1JWqv666hReGHrmZJGx0xQTM9vPD1HN5t2U3KTfhO/mTlAUWVyg9tCt +OzboKgs1AgMBAAECggEBAKLj6IOJBKXolczpzb8UkyAjAkGBektcseV07gelJ/fk +3z0LuWPv5p12E/HlXB24vU2x/ikUbbP3eMsawRzDEahQqmNmPEkYAYUAy/Qpi9GN +DYvn3LqDec4jVgeQKS+p9H2DzUpTogp8zR2//yzbuWBg2+F//xh7vU0S0RQCziPM +x7RSBgbhxSfChfEJbS2sDnzfh0jRQmoY95iFv7puet1FJtzdZ4fgCd1RqmC2lFM5 +H0eZtN/Cz19lieVs0b996DErdEBqClVZO00eYbRozCDaBzRU3ybB/dMrGJxhkkXm +wb3kWMtziH9qOYsostuHIFu8eKFLloKxFnq2R4DGxOECgYEA2KUIZISOeGJSBcLJ +JAUK2gvgXPNo4HHWIwOA9xeN3ZJlsnPlffXQNnm6t1st1V2gfMm9I2n0m/F0y2B/ +n/XGSa8bghfPA9l0c2h58lkL3JQJR/paa8ycTz+YZPrznEyN7Qa0RrJXUvZv9lQL +Hc3+FHcSHgMqDV2f2bHAEu9YGi0CgYEAx6VEIPNvrHFgjo/jk1RTuk+m0xEWQsZL +Cs+izQMr2TaeJn8LG+93AvFuYn0J0nT3WuStLPrUg8i4IhSS6lf1tId5ivIZPm4r +YwMyblBJXhnHbk7Uqodjfw/3s6V2HAu++B7hTdyVr9DFuST9uv4m8bkPV8rfX1jE +I2rAPVWvgikCgYB+wNAQP547wQrMZBLbCDg5KwmyWJfb+b6X7czexOEz6humNTjo +YZHYzY/5B1fhpk3ntQD8X1nGg5caBvOk21+QbOtjShrM3cXMYCw5JvBRtitX+Zo9 +yBEMLOE0877ki8XeEDYZxu5gk98d+D4oygUGZEQtWxyXhVepPt5qNa8OYQKBgQDH +RVgZI6KFlqzv3wMh3PutbS9wYQ+9GrtwUQuIYe/0YSW9+vSVr5E0qNKrD28sV39F +hBauXLady0yvB6YUrjMbPFW+sCMuQzyfGWPO4+g3OrfqjFiM1ZIkE0YEU9Tt7XNx +qTDtTI1D7bhNMnTnniI1B6ge0und+3XafAThs5L48QKBgQCTTpfqMt8kU3tcI9sf +0MK03y7kA76d5uw0pZbWFy7KI4qnzWutCzb+FMPWWsoFtLJLPZy//u/ZCUVFVa4d +0Y/ASNQIESVPXFLAltlLo4MSmsg1vCBsbviEEaPeEjvMrgki93pYtd/aOSgkYC1T +mEq154s5rmqh+h+XRIf7Au0SLw== +-----END PRIVATE KEY----- From c4003907264fb20f73098e59f0514f27368374f7 Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Tue, 14 Jan 2020 11:02:12 -0800 Subject: [PATCH 2/9] added example and updated docs --- examples/simple-server-pkcs8.rs | 43 +++++++++++++++++++++++++++++++++ src/lib.rs | 14 ++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 examples/simple-server-pkcs8.rs diff --git a/examples/simple-server-pkcs8.rs b/examples/simple-server-pkcs8.rs new file mode 100644 index 00000000..8e1b1b81 --- /dev/null +++ b/examples/simple-server-pkcs8.rs @@ -0,0 +1,43 @@ +extern crate native_tls; + +use native_tls::{Identity, TlsAcceptor, TlsStream}; +use std::fs::File; +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::sync::Arc; +use std::thread; + +fn main() { + let mut cert_file = File::open("test/cert.pem").unwrap(); + let mut certs = vec![]; + cert_file.read_to_end(&mut certs).unwrap(); + let mut key_file = File::open("test/key.pem").unwrap(); + let mut key = vec![]; + key_file.read_to_end(&mut key).unwrap(); + let pkcs8 = Identity::from_pkcs8(&certs, &key).unwrap(); + + let acceptor = TlsAcceptor::new(pkcs8).unwrap(); + let acceptor = Arc::new(acceptor); + + let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); + + fn handle_client(mut stream: TlsStream) { + let mut buf = [0; 1024]; + let read = stream.read(&mut buf).unwrap(); + let received = std::str::from_utf8(&buf[0..read]).unwrap(); + stream.write_all(format!("received '{}'", received).as_bytes()).unwrap(); + } + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + let acceptor = acceptor.clone(); + thread::spawn(move || { + let stream = acceptor.accept(stream).unwrap(); + handle_client(stream); + }); + } + Err(_e) => { /* connection failed */ } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 01230d3a..ac460213 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ //! * TLS/SSL client communication //! * TLS/SSL server communication //! * PKCS#12 encoded identities +//! * PKCS#8 encoded identities //! * Secure-by-default for client and server //! * Includes hostname verification for clients //! * Supports asynchronous I/O for both the server and the client @@ -186,10 +187,15 @@ impl Identity { Ok(Identity(identity)) } - /// buf is the contents of a file containing a chain of PEM encoded certificates - /// key is the contents of a file containing a PEM encoded private key - pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { - let identity = imp::Identity::from_pkcs8(buf, key)?; + /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first. + /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate. + /// + /// The certificate chain should contain any intermediate cerficates that should be sent to + /// clients to allow them to build a chain to a trusted root. + /// + /// A certificate chain here means a series of PEM encoded certificates concatenated together. + pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { + let identity = imp::Identity::from_pkcs8(pem, key)?; Ok(Identity(identity)) } } From 1a6d7ba4de94357446548be1f1929d59ca4a131d Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Tue, 14 Jan 2020 13:03:32 -0800 Subject: [PATCH 3/9] revert unnecessary changes --- src/imp/openssl.rs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index 3556802b..cec32928 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -10,7 +10,7 @@ use self::openssl::ssl::{ self, MidHandshakeSslStream, SslAcceptor, SslConnector, SslContextBuilder, SslMethod, SslVerifyMode, }; -use self::openssl::x509::{X509VerifyResult, X509}; +use self::openssl::x509::{X509, X509VerifyResult}; use std::error; use std::fmt; use std::io; @@ -155,7 +155,7 @@ impl From for Error { pub struct Identity { pkey: PKey, cert: X509, - chain: Option>, + chain: Vec, } impl Identity { @@ -165,19 +165,19 @@ impl Identity { Ok(Identity { pkey: parsed.pkey, cert: parsed.cert, - chain: parsed.chain.map(|stack| stack.into_iter().collect()), + chain: parsed.chain.into_iter().flat_map(|x| x).collect(), }) } pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { let pkey = PKey::private_key_from_pem(key)?; - let p_block = pem::PemBlock::new(buf); - let mut chain: Vec = p_block.map(|buf| X509::from_pem(buf).unwrap()).collect(); - let cert = chain.pop(); + let mut cert_chain = pem::PemBlock::new(buf).map(|buf| X509::from_pem(buf).unwrap()); + let cert = cert_chain.next(); + let chain = cert_chain.collect(); Ok(Identity { pkey, cert: cert.expect("need identity cert"), - chain: Some(chain), + chain: chain, }) } } @@ -277,10 +277,11 @@ impl TlsConnector { if let Some(ref identity) = builder.identity { connector.set_certificate(&identity.0.cert)?; connector.set_private_key(&identity.0.pkey)?; - if let Some(ref chain) = identity.0.chain { - for cert in chain.iter().rev() { - connector.add_extra_chain_cert(cert.to_owned())?; - } + for cert in identity.0.chain.iter() { + // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html + // specifies that "When sending a certificate chain, extra chain certificates are + // sent in order following the end entity certificate." + connector.add_extra_chain_cert(cert.to_owned())?; } } supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?; @@ -328,10 +329,11 @@ impl TlsAcceptor { let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; acceptor.set_private_key(&builder.identity.0.pkey)?; acceptor.set_certificate(&builder.identity.0.cert)?; - if let Some(ref chain) = builder.identity.0.chain { - for cert in chain.iter().rev() { - acceptor.add_extra_chain_cert(cert.to_owned())?; - } + for cert in builder.identity.0.chain.iter() { + // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html + // specifies that "When sending a certificate chain, extra chain certificates are + // sent in order following the end entity certificate." + acceptor.add_extra_chain_cert(cert.to_owned())?; } supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?; From ab437baa8aaddabbf05a5894f36babf748eb8912 Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Tue, 14 Jan 2020 14:15:17 -0800 Subject: [PATCH 4/9] add client auth to test --- src/test.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test.rs b/src/test.rs index 15b68b9c..46087525 100644 --- a/src/test.rs +++ b/src/test.rs @@ -383,6 +383,7 @@ mod tests { let cert = include_bytes!("../test/cert.pem"); let ident = Identity::from_pkcs8(cert, key).unwrap(); + let ident2 = ident.clone(); let builder = p!(TlsAcceptor::new(ident)); let listener = p!(TcpListener::bind("0.0.0.0:0")); @@ -404,6 +405,13 @@ mod tests { let socket = p!(TcpStream::connect(("localhost", port))); let mut builder = TlsConnector::builder(); + // FIXME + // This checks that we can successfully add a certificate on the client side. + // Unfortunately, we can not request client certificates through the API of this library, + // otherwise we could check in the server thread that + // socket.peer_certificate().unwrap().is_some() + builder.identity(ident2); + builder.add_root_certificate(root_ca); let builder = p!(builder.build()); let mut socket = p!(builder.connect("foobar.com", socket)); From beb8ebc136bc89f3723e17c6fb31e1b821a2310f Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Tue, 14 Jan 2020 14:39:22 -0800 Subject: [PATCH 5/9] cleaned up unwraps and expects --- src/imp/openssl.rs | 9 +++++++-- src/imp/schannel.rs | 14 ++++++-------- src/imp/security_framework.rs | 3 +-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index cec32928..7dfe5c67 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -171,12 +171,17 @@ impl Identity { pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { let pkey = PKey::private_key_from_pem(key)?; - let mut cert_chain = pem::PemBlock::new(buf).map(|buf| X509::from_pem(buf).unwrap()); + let mut cert_chain = vec!(); + for buf in pem::PemBlock::new(buf) { + cert_chain.push(X509::from_pem(buf)?); + } + let mut cert_chain = cert_chain.into_iter(); let cert = cert_chain.next(); let chain = cert_chain.collect(); Ok(Identity { pkey, - cert: cert.expect("need identity cert"), + // an identity must have at least one certificate, the leaf cert + cert: cert.expect("at least one certificate must be provided to create an identity"), chain: chain, }) } diff --git a/src/imp/schannel.rs b/src/imp/schannel.rs index ec1be964..b3e235ba 100644 --- a/src/imp/schannel.rs +++ b/src/imp/schannel.rs @@ -101,8 +101,8 @@ impl Identity { pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { let mut store = Memory::new()?.into_store(); let mut cert_iter = crate::pem::PemBlock::new(pem).into_iter(); - let leaf = cert_iter.next().unwrap(); - let cert = CertContext::from_pem(std::str::from_utf8(leaf).unwrap()).unwrap(); + let leaf = cert_iter.next().expect("at least one certificate must be provided to create an identity"); + let cert = CertContext::from_pem(std::str::from_utf8(leaf).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "leaf cert contains invalid utf8"))?)?; let mut options = AcquireOptions::new(); options.container("schannel"); @@ -110,20 +110,18 @@ impl Identity { let mut container = match options.acquire(type_) { Ok(container) => container, - Err(_) => options.new_keyset(true).acquire(type_).unwrap(), + Err(_) => options.new_keyset(true).acquire(type_)?, }; - let key = crate::pem::pem_to_der(key, Some(crate::pem::PEM_PRIVATE_KEY)).unwrap(); + let key = crate::pem::pem_to_der(key, Some(crate::pem::PEM_PRIVATE_KEY)).expect("invalid PKCS8 key provided"); container.import() - .import_pkcs8(&key) - .unwrap(); + .import_pkcs8(&key)?; cert.set_key_prov_info() .container("schannel") .type_(type_) .keep_open(true) .key_spec(KeySpec::key_exchange()) - .set() - .unwrap(); + .set()?; let mut context = store.add_cert(&cert, CertAdd::Always)?; for int_cert in cert_iter { diff --git a/src/imp/security_framework.rs b/src/imp/security_framework.rs index 886e1b1b..4fd7724b 100644 --- a/src/imp/security_framework.rs +++ b/src/imp/security_framework.rs @@ -91,8 +91,7 @@ impl Identity { let dir = TempDir::new().unwrap(); let keychain = keychain::CreateOptions::new() .password("password") - .create(dir.path().join("identity.keychain")) - .unwrap(); + .create(dir.path().join("identity.keychain"))?; let mut items = SecItems::default(); From 28eade4430e08b147fc35d0e6996413671f9a9f1 Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Tue, 4 Feb 2020 14:29:06 -0800 Subject: [PATCH 6/9] use underlying libraries for pem parsing --- Cargo.toml | 2 +- src/imp/openssl.rs | 7 +-- src/imp/schannel.rs | 3 +- src/pem.rs | 107 -------------------------------------------- 4 files changed, 3 insertions(+), 116 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ad06b98..1879f259 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ libc = "0.2" tempfile = "3.0" [target.'cfg(target_os = "windows")'.dependencies] -schannel = "0.1.16" +schannel = "0.1.17" [target.'cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))'.dependencies] log = "0.4.5" diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index 7dfe5c67..cb4f43f5 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -15,7 +15,6 @@ use std::error; use std::fmt; use std::io; use std::sync::{Once, ONCE_INIT}; -use pem; use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; @@ -171,11 +170,7 @@ impl Identity { pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { let pkey = PKey::private_key_from_pem(key)?; - let mut cert_chain = vec!(); - for buf in pem::PemBlock::new(buf) { - cert_chain.push(X509::from_pem(buf)?); - } - let mut cert_chain = cert_chain.into_iter(); + let mut cert_chain = X509::stack_from_pem(buf)?.into_iter(); let cert = cert_chain.next(); let chain = cert_chain.collect(); Ok(Identity { diff --git a/src/imp/schannel.rs b/src/imp/schannel.rs index b3e235ba..81217f4f 100644 --- a/src/imp/schannel.rs +++ b/src/imp/schannel.rs @@ -112,9 +112,8 @@ impl Identity { Ok(container) => container, Err(_) => options.new_keyset(true).acquire(type_)?, }; - let key = crate::pem::pem_to_der(key, Some(crate::pem::PEM_PRIVATE_KEY)).expect("invalid PKCS8 key provided"); container.import() - .import_pkcs8(&key)?; + .import_pkcs8_pem(&key)?; cert.set_key_prov_info() .container("schannel") diff --git a/src/pem.rs b/src/pem.rs index 93e0103b..0d30ef7e 100644 --- a/src/pem.rs +++ b/src/pem.rs @@ -35,67 +35,6 @@ lazy_static!{ }; } -/// Convert PEM to DER. If `guard` is specified (e.g. as PEM_CERTIFICATE), then the guardlines are -/// verified to match the expected string. Otherwise, the guardlines are verified to generally have -/// the correct form. -/// -/// On failure (due to guardlines syntax or an illegal PEM character), returns None. -pub fn pem_to_der>(pem: &T, guard: Option<&PemGuard>) -> Option> { - let pem = match std::str::from_utf8(pem.as_ref()) { - Err(_) => return None, - Ok(p) => p, - }; - let pem = match pem.find("-----") { - Some(i) => pem.split_at(i).1, - None => return None, - }; - let mut lines = pem.lines(); - - let begin = match lines.next() { - Some(l) => l, - None => return None, - }; - let end = match lines.last() { - Some(l) => l, - None => return None, - }; - - if let Some(g) = guard { - if begin != g.begin || end != g.end { - return None; - } - } else { - if !begin.starts_with("-----BEGIN ") || !begin.ends_with("-----") || - !end.starts_with("-----END") || !end.ends_with("-----") { - return None; - } - } - - let body_start = pem.char_indices() - .skip(begin.len()) - .skip_while(|t| t.1.is_whitespace()) - .next().unwrap().0; - let body_end = pem.rmatch_indices(&end).next().unwrap().0; - pem[body_start..body_end].from_base64().ok() -} - -/// Convert DER to PEM. The guardlines use the identifying string chosen by `guard` -/// (e.g. PEM_CERTIFICATE). -pub fn der_to_pem>(der: &T, guard: &PemGuard) -> String { - let mut pem = String::new(); - - pem.push_str(guard.begin); - pem.push('\n'); - if der.as_ref().len() > 0 { - pem.push_str(&der.as_ref().to_base64(*BASE64_PEM)); - pem.push('\n'); - } - pem.push_str(guard.end); - pem.push('\n'); - - pem -} - /// Split data by PEM guard lines pub struct PemBlock<'a> { pem_block: &'a str, @@ -129,52 +68,6 @@ impl<'a> Iterator for PemBlock<'a> { } } -#[test] -fn test_pem() { - assert!(pem_to_der("", None).is_none()); - assert!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----END JUNK-----\n", Some(PEM_CERTIFICATE)).is_none()); - assert!(pem_to_der("-----BEGIN JUNK-----\n-----END CERTIFICATE-----\n", Some(PEM_CERTIFICATE)).is_none()); - assert_eq!(pem_to_der("-----BEGIN JUNK-----\n-----END GARBAGE-----\n", None).unwrap(), vec![]); - assert_eq!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n", None).unwrap(), vec![]); - assert!(pem_to_der("-----EGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n", None).is_none()); - assert!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----ND CERTIFICATE-----\n", None).is_none()); - assert!(pem_to_der("-----BEGIN CERTIFICATE----\n-----END CERTIFICATE-----\n", None).is_none()); - assert!(pem_to_der("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE----\n", None).is_none()); - assert_eq!(pem_to_der("-----BEGIN JUNK-----\n\ - AAECAwQFBgcICQoLDA0ODw==\n\ - -----END GARBAGE-----\n", None).unwrap(), - vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - assert_eq!(pem_to_der("-----BEGIN CERTIFICATE-----\n\ - AAECAwQFBgcICQoLDA0ODw==\n\ - -----END CERTIFICATE-----\n", None).unwrap(), - vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); -} - -#[test] -fn test_roundtrip_whole_line() { - // Test the case where the PEM is a multiple of whole lines. - let test_cert = -"-----BEGIN CERTIFICATE----- -MIIHBTCCBe2gAwIBAgIRAIFsdIAf8kR29DFR7K4znoIwDQYJKoZIhvcNAQELBQAw ------END CERTIFICATE----- -"; - - assert_eq!(der_to_pem(&pem_to_der(test_cert, Some(PEM_CERTIFICATE)).unwrap(), PEM_CERTIFICATE), test_cert); -} - -#[test] -fn test_wrapping() { - let mut v: Vec = vec![]; - let bytes_per_line = BASE64_PEM_WRAP * 3 / 4; - for i in 0..2*bytes_per_line { - let pem = der_to_pem(&v, PEM_CERTIFICATE); - // Check that we can do a round trip, and that we got the expected number of lines. - assert_eq!(pem_to_der(&pem, Some(PEM_CERTIFICATE)).unwrap(), v); - assert_eq!(pem.matches("\n").count(), 2 + (i + bytes_per_line - 1) / bytes_per_line); - v.push(0); - } -} - #[test] fn test_split() { // Split three certs, CRLF line terminators. From 7e9a6123acf476a582659bbc1b3e33440bddad6e Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Wed, 12 Feb 2020 13:48:39 -0800 Subject: [PATCH 7/9] address some review comments --- Cargo.toml | 5 +---- src/lib.rs | 4 ++-- src/pem.rs | 35 ----------------------------------- 3 files changed, 3 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1879f259..03b7acbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,10 @@ readme = "README.md" [features] vendored = ["openssl/vendored"] -[dependencies] -lazy_static = "1.0" -rustc-serialize = "0.3" - [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] security-framework = "0.3.1" security-framework-sys = "0.3.1" +lazy_static = "1.0" libc = "0.2" tempfile = "3.0" diff --git a/src/lib.rs b/src/lib.rs index ac460213..641b8a04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ //! * TLS/SSL client communication //! * TLS/SSL server communication //! * PKCS#12 encoded identities -//! * PKCS#8 encoded identities +//! * X.509/PKCS#8 encoded identities //! * Secure-by-default for client and server //! * Includes hostname verification for clients //! * Supports asynchronous I/O for both the server and the client @@ -98,12 +98,12 @@ #![warn(missing_docs)] #[macro_use] +#[cfg(any(target_os = "macos", target_os = "ios"))] extern crate lazy_static; #[cfg(test)] extern crate hex; -extern crate rustc_serialize; mod pem; use std::any::Any; diff --git a/src/pem.rs b/src/pem.rs index 0d30ef7e..fe41e7ca 100644 --- a/src/pem.rs +++ b/src/pem.rs @@ -1,39 +1,4 @@ #![allow(unused)] -use rustc_serialize::base64::{self, FromBase64, ToBase64}; - -/// Type of the various `PEM_*` constants supplied to `pem_to_der` / `der_to_pem`. -pub struct PemGuard { - begin: &'static str, - end: &'static str, -} - -macro_rules! pem_guard { - ($n:expr) => { - &PemGuard { - begin: concat!("-----BEGIN ", $n, "-----"), - end: concat!("-----END ", $n, "-----"), - } - } -} - -// Ref. RFC7468, although these are not universally respected. -pub const PEM_CERTIFICATE: &'static PemGuard = pem_guard!("CERTIFICATE"); -pub const PEM_CERTIFICATE_REQUEST: &'static PemGuard = pem_guard!("CERTIFICATE REQUEST"); -pub const PEM_ENCRYPTED_PRIVATE_KEY: &'static PemGuard = pem_guard!("ENCRYPTED PRIVATE KEY"); -pub const PEM_PRIVATE_KEY: &'static PemGuard = pem_guard!("PRIVATE KEY"); -pub const PEM_PUBLIC_KEY: &'static PemGuard = pem_guard!("PUBLIC KEY"); -pub const PEM_CMS: &'static PemGuard = pem_guard!("CMS"); - -const BASE64_PEM_WRAP: usize = 64; - -lazy_static!{ - static ref BASE64_PEM: base64::Config = base64::Config { - char_set: base64::CharacterSet::Standard, - newline: base64::Newline::LF, - pad: true, - line_length: Some(BASE64_PEM_WRAP), - }; -} /// Split data by PEM guard lines pub struct PemBlock<'a> { From da4cf1aab2861a66aa523019eaa036e547d2ea93 Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Tue, 18 Feb 2020 11:02:45 -0800 Subject: [PATCH 8/9] disable README tests, moved pem mod into schannel --- README.md | 4 +-- src/imp/schannel.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++- src/pem.rs | 78 ------------------------------------------ 3 files changed, 84 insertions(+), 81 deletions(-) delete mode 100644 src/pem.rs diff --git a/README.md b/README.md index 8603edd0..7c2d2697 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ native-tls = "0.2" An example client looks like: -```rust +```rust,ignore extern crate native_tls; use native_tls::TlsConnector; @@ -48,7 +48,7 @@ fn main() { To accept connections as a server from remote clients: -```rust,no_run +```rust,ignore extern crate native_tls; use native_tls::{Identity, TlsAcceptor, TlsStream}; diff --git a/src/imp/schannel.rs b/src/imp/schannel.rs index 81217f4f..d5838917 100644 --- a/src/imp/schannel.rs +++ b/src/imp/schannel.rs @@ -100,7 +100,7 @@ impl Identity { pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { let mut store = Memory::new()?.into_store(); - let mut cert_iter = crate::pem::PemBlock::new(pem).into_iter(); + let mut cert_iter = pem::PemBlock::new(pem).into_iter(); let leaf = cert_iter.next().expect("at least one certificate must be provided to create an identity"); let cert = CertContext::from_pem(std::str::from_utf8(leaf).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "leaf cert contains invalid utf8"))?)?; @@ -378,3 +378,84 @@ impl io::Write for TlsStream { self.0.flush() } } + + +mod pem { + /// Split data by PEM guard lines + pub struct PemBlock<'a> { + pem_block: &'a str, + cur_end: usize, + } + + impl<'a> PemBlock<'a> { + pub fn new(data: &'a [u8]) -> PemBlock<'a> { + let s = ::std::str::from_utf8(data).unwrap(); + PemBlock { + pem_block: s, + cur_end: s.find("-----BEGIN").unwrap_or(s.len()), + } + } + } + + impl<'a> Iterator for PemBlock<'a> { + type Item = &'a [u8]; + fn next(&mut self) -> Option { + let last = self.pem_block.len(); + if self.cur_end >= last { + return None; + } + let begin = self.cur_end; + let pos = self.pem_block[begin + 1..].find("-----BEGIN"); + self.cur_end = match pos { + Some(end) => end + begin + 1, + None => last, + }; + return Some(&self.pem_block[begin..self.cur_end].as_bytes()); + } + } + + #[test] + fn test_split() { + // Split three certs, CRLF line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ + -----BEGIN SECOND-----\r\n-----END SECOND\r\n\ + -----BEGIN THIRD-----\r\n-----END THIRD\r\n").collect::>(), + vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], + b"-----BEGIN SECOND-----\r\n-----END SECOND\r\n", + b"-----BEGIN THIRD-----\r\n-----END THIRD\r\n"]); + // Split three certs, CRLF line terminators except at EOF. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ + -----BEGIN SECOND-----\r\n-----END SECOND-----\r\n\ + -----BEGIN THIRD-----\r\n-----END THIRD-----").collect::>(), + vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], + b"-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n", + b"-----BEGIN THIRD-----\r\n-----END THIRD-----"]); + // Split two certs, LF line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ + -----BEGIN SECOND-----\n-----END SECOND\n").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], + b"-----BEGIN SECOND-----\n-----END SECOND\n"]); + // Split two certs, CR line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r-----END FIRST-----\r\ + -----BEGIN SECOND-----\r-----END SECOND\r").collect::>(), + vec![b"-----BEGIN FIRST-----\r-----END FIRST-----\r" as &[u8], + b"-----BEGIN SECOND-----\r-----END SECOND\r"]); + // Split two certs, LF line terminators except at EOF. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ + -----BEGIN SECOND-----\n-----END SECOND").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], + b"-----BEGIN SECOND-----\n-----END SECOND"]); + // Split a single cert, LF line terminators. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8]]); + // Split a single cert, LF line terminators except at EOF. + assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----").collect::>(), + vec![b"-----BEGIN FIRST-----\n-----END FIRST-----" as &[u8]]); + // (Don't) split garbage. + assert_eq!(PemBlock::new(b"junk").collect::>(), + Vec::<&[u8]>::new()); + assert_eq!(PemBlock::new(b"junk-----BEGIN garbage").collect::>(), + vec![b"-----BEGIN garbage" as &[u8]]); + } +} + diff --git a/src/pem.rs b/src/pem.rs deleted file mode 100644 index fe41e7ca..00000000 --- a/src/pem.rs +++ /dev/null @@ -1,78 +0,0 @@ -#![allow(unused)] - -/// Split data by PEM guard lines -pub struct PemBlock<'a> { - pem_block: &'a str, - cur_end: usize, -} - -impl<'a> PemBlock<'a> { - pub fn new(data: &'a [u8]) -> PemBlock<'a> { - let s = ::std::str::from_utf8(data).unwrap(); - PemBlock { - pem_block: s, - cur_end: s.find("-----BEGIN").unwrap_or(s.len()), - } - } -} - -impl<'a> Iterator for PemBlock<'a> { - type Item = &'a [u8]; - fn next(&mut self) -> Option { - let last = self.pem_block.len(); - if self.cur_end >= last { - return None; - } - let begin = self.cur_end; - let pos = self.pem_block[begin + 1..].find("-----BEGIN"); - self.cur_end = match pos { - Some(end) => end + begin + 1, - None => last, - }; - return Some(&self.pem_block[begin..self.cur_end].as_bytes()); - } -} - -#[test] -fn test_split() { - // Split three certs, CRLF line terminators. - assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ - -----BEGIN SECOND-----\r\n-----END SECOND\r\n\ - -----BEGIN THIRD-----\r\n-----END THIRD\r\n").collect::>(), - vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], - b"-----BEGIN SECOND-----\r\n-----END SECOND\r\n", - b"-----BEGIN THIRD-----\r\n-----END THIRD\r\n"]); - // Split three certs, CRLF line terminators except at EOF. - assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ - -----BEGIN SECOND-----\r\n-----END SECOND-----\r\n\ - -----BEGIN THIRD-----\r\n-----END THIRD-----").collect::>(), - vec![b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], - b"-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n", - b"-----BEGIN THIRD-----\r\n-----END THIRD-----"]); - // Split two certs, LF line terminators. - assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ - -----BEGIN SECOND-----\n-----END SECOND\n").collect::>(), - vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], - b"-----BEGIN SECOND-----\n-----END SECOND\n"]); - // Split two certs, CR line terminators. - assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\r-----END FIRST-----\r\ - -----BEGIN SECOND-----\r-----END SECOND\r").collect::>(), - vec![b"-----BEGIN FIRST-----\r-----END FIRST-----\r" as &[u8], - b"-----BEGIN SECOND-----\r-----END SECOND\r"]); - // Split two certs, LF line terminators except at EOF. - assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ - -----BEGIN SECOND-----\n-----END SECOND").collect::>(), - vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], - b"-----BEGIN SECOND-----\n-----END SECOND"]); - // Split a single cert, LF line terminators. - assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n").collect::>(), - vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8]]); - // Split a single cert, LF line terminators except at EOF. - assert_eq!(PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----").collect::>(), - vec![b"-----BEGIN FIRST-----\n-----END FIRST-----" as &[u8]]); - // (Don't) split garbage. - assert_eq!(PemBlock::new(b"junk").collect::>(), - Vec::<&[u8]>::new()); - assert_eq!(PemBlock::new(b"junk-----BEGIN garbage").collect::>(), - vec![b"-----BEGIN garbage" as &[u8]]); -} From 255dd5493b446755a9e40be3a4638afedfe67b03 Mon Sep 17 00:00:00 2001 From: Dario Gonzalez Date: Tue, 18 Feb 2020 11:32:38 -0800 Subject: [PATCH 9/9] added multiple keys test --- src/lib.rs | 2 -- src/test.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/cert2.pem | 32 ++++++++++++++++++++++ test/key2.pem | 52 +++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 test/cert2.pem create mode 100644 test/key2.pem diff --git a/src/lib.rs b/src/lib.rs index 641b8a04..172e220f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,8 +104,6 @@ extern crate lazy_static; #[cfg(test)] extern crate hex; -mod pem; - use std::any::Any; use std::error; use std::fmt; diff --git a/src/test.rs b/src/test.rs index 46087525..a2f1f8a9 100644 --- a/src/test.rs +++ b/src/test.rs @@ -423,4 +423,77 @@ mod tests { p!(j.join()); } + #[test] + fn two_servers() { + let key = include_bytes!("../test/key.pem"); + let cert = include_bytes!("../test/cert.pem"); + let identity = p!(Identity::from_pkcs8(cert, key)); + let builder = TlsAcceptor::builder(identity); + let builder = p!(builder.build()); + + let listener = p!(TcpListener::bind("0.0.0.0:0")); + let port = p!(listener.local_addr()).port(); + + let j = thread::spawn(move || { + let socket = p!(listener.accept()).0; + let mut socket = p!(builder.accept(socket)); + + let mut buf = [0; 5]; + p!(socket.read_exact(&mut buf)); + assert_eq!(&buf, b"hello"); + + p!(socket.write_all(b"world")); + }); + + let key = include_bytes!("../test/key2.pem"); + let cert = include_bytes!("../test/cert2.pem"); + let identity = p!(Identity::from_pkcs8(cert, key)); + let builder = TlsAcceptor::builder(identity); + let builder = p!(builder.build()); + + let listener = p!(TcpListener::bind("0.0.0.0:0")); + let port2 = p!(listener.local_addr()).port(); + + let j2 = thread::spawn(move || { + let socket = p!(listener.accept()).0; + let mut socket = p!(builder.accept(socket)); + + let mut buf = [0; 5]; + p!(socket.read_exact(&mut buf)); + assert_eq!(&buf, b"hello"); + + p!(socket.write_all(b"world")); + }); + + let root_ca = include_bytes!("../test/root-ca.pem"); + let root_ca = p!(Certificate::from_pem(root_ca)); + + let socket = p!(TcpStream::connect(("localhost", port))); + let mut builder = TlsConnector::builder(); + builder.add_root_certificate(root_ca); + let builder = p!(builder.build()); + let mut socket = p!(builder.connect("foobar.com", socket)); + + p!(socket.write_all(b"hello")); + let mut buf = vec![]; + p!(socket.read_to_end(&mut buf)); + assert_eq!(buf, b"world"); + + let root_ca = include_bytes!("../test/cert2.pem"); + let root_ca = p!(Certificate::from_pem(root_ca)); + + let socket = p!(TcpStream::connect(("localhost", port2))); + let mut builder = TlsConnector::builder(); + builder.add_root_certificate(root_ca); + let builder = p!(builder.build()); + let mut socket = p!(builder.connect("foobar.com", socket)); + + p!(socket.write_all(b"hello")); + let mut buf = vec![]; + p!(socket.read_to_end(&mut buf)); + assert_eq!(buf, b"world"); + + p!(j.join()); + p!(j2.join()); + } } diff --git a/test/cert2.pem b/test/cert2.pem new file mode 100644 index 00000000..d9fbcd57 --- /dev/null +++ b/test/cert2.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIJAOFl3cFizu8kMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMCmZvb2Jhci5jb20wHhcNMTcwNjI0MTYw +NzEwWhcNMjcwNjIyMTYwNzEwWjBaMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29t +ZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYD +VQQDDApmb29iYXIuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +2TfYpOOpb1HigFQOHq756Hr/wE2YvTGRRmFOuI/7TEzZnG4TcYGqbS8UTNIvxaeU +GtQdRLFV6QWtIQhArHOU22D0EWxj88MYE7O3h5csZOUKfI0s4IRA/u3VP2mfk/VT +AbikUJH370tTpxltcpMoOgOWkxraF6LDCnSSGALU6MN23OySUjadd4jtv5BmqdQL +t5+VDOsgEK8Gq0Kvw4noyADPAWRj/7L/OyJ2nk9Krlof/FYb+c7lK27LUqEM3yjZ +nARO+zCFj0cWqd0uYY3Uz+My73CLLu8NeHtl0Qv9a23jtol76j7wUXjrIR5VBs0o +hhP1/zcOfuXu2NYzHa6gbefJqnUtqMgnibimQyMZaLxoVOryJKkEHj/D/U3OgbkW +3q4sWZ2xGp6FlVU8smgPcq5KMQ/BQzPcGciVYKbHl+H6TUgSdABga8yMxURK058t +TZnv/GaSKEVjM8VNLuiZAlmRtzBbd5cbRMIb/8SV6m5xwK4jQ0pdDtKbY8I0poku +YbT/B8Rs/x50LgCk/sEjWCEdoxZ/KY4nuYpGzs5B79LZn4ofmHRG1IfiQBPxuR4S +ChW5V+HaICXsL2oKhxIhpDQjtownj9PF85ew6IODNzk/oHDZgFCJwHK23Acr+kTt +AsI/Oht3W0eG34DHS2UPuVc1jGE5TaNK3F/AN8ICANUCAwEAAaNTMFEwHQYDVR0O +BBYEFNlnR/6xSAyj2shdqYPPRbOpFj9AMB8GA1UdIwQYMBaAFNlnR/6xSAyj2shd +qYPPRbOpFj9AMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAAd/ +RYi40if/HiOLjVWeu4ETpZLut6p4NnFY2ueRF7O3cNSH3ugQ0Ucc4S8rDrTGHryz +jTVHC1p0783u7VkOiJNFKT+y5RmGVSRLnXfbWY4Vwpz5Dqz6sZtZlVO22VqhfT0K +Cwzbu6Mgr/rhPow3tCWUDzDVoKPnJCFmDwHWcXlDCjnuU/0DdgO+q3anj1x3Zt7x +1cXG+2O2PO8474A1ESujTYduCnQRzEOeud4beGhLeW6wcuq2tNRwbxQzBdCzb4/f +INSDsqk6lpimpokxVh9Q8yUSbORvPyXMLb0F2I670CBfsQ27U/+xqBPU7rmz63vL +J70fee+QQQ9KF9O1LWeIaPkRyKzU0kovwLTsTVXV6xQUeH1PLdgAkhh5KTnWReUq +n7Iu8EGIHNRQFGp/qgo7bww+g2aWiqyCquCCe5zyt13j5Ev9naRJzROna39BocsG +t2/NHSNMJ6ru4mPunPrP25J5V2qT7sN9RrKEPaANAV3dWuUOhvlrHQBVXKACckTJ +K+mKXP0pXrnLhQc6RP4ep8JuDY7qlRnJwr3+WxeTb52wcjmz0RusdbiEeBhgMcnF +ZDpZmLeTTPH/50TVvRTXgYGFmlgzVEqqGwpJsnEISuhgI/x58FqiXFCEaz9zr+xi +QWt2BCQJnE0G1UV9msEybRE4toiCHdOpojJjHFi8 +-----END CERTIFICATE----- diff --git a/test/key2.pem b/test/key2.pem new file mode 100644 index 00000000..3d21e448 --- /dev/null +++ b/test/key2.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +QYXuzBi7LlSFYIOmdDy+5XZ6N4hTXtE= +-----END PRIVATE KEY-----