verify certs with webpki

This commit is contained in:
Joseph Birr-Pixton 2016-05-21 12:43:01 +01:00
parent 9ac839a44b
commit 2a9dd6f801
13 changed files with 206 additions and 18 deletions

20
Cargo.lock generated
View File

@ -4,6 +4,8 @@ version = "0.1.0"
dependencies = [
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.1.0 (git+https://github.com/briansmith/ring)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"webpki 0.1.0 (git+https://github.com/briansmith/webpki)",
]
[[package]]
@ -30,6 +32,11 @@ dependencies = [
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.11"
@ -143,8 +150,9 @@ dependencies = [
[[package]]
name = "ring"
version = "0.1.0"
source = "git+https://github.com/briansmith/ring#a3d5424a78b200a92a5fcc06f57c72e03dee90ad"
source = "git+https://github.com/briansmith/ring#157dd0160dc9bb0ae7fe0028771409630986785d"
dependencies = [
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -169,6 +177,16 @@ dependencies = [
"winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "webpki"
version = "0.1.0"
source = "git+https://github.com/briansmith/webpki#a157f83b0d7aff9bcc5b05236de58ca1979a022c"
dependencies = [
"ring 0.1.0 (git+https://github.com/briansmith/ring)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.2.7"

View File

@ -5,4 +5,7 @@ authors = ["Joseph Birr-Pixton <jpixton@gmail.com>"]
[dependencies]
ring = { version = "0.1.0", git = "https://github.com/briansmith/ring" }
webpki = { version = "0.1.0", git = "https://github.com/briansmith/webpki" }
mio = "0.5.1"
time = "0.1.35"

View File

@ -48,14 +48,27 @@ impl mio::Handler for TlsClient {
}
}
fn read_file(filename: &str) -> Vec<u8> {
use std::io::Read;
let mut r = Vec::new();
let mut f = std::fs::File::open(filename).unwrap();
f.read_to_end(&mut r).unwrap();
r
}
impl TlsClient {
fn new(sock: TcpStream) -> TlsClient {
let config = Arc::new(rustls::client::ClientConfig::default());
let mut config = rustls::client::ClientConfig::default();
config.root_store.add(&read_file("test/ca.der"))
.unwrap();
let cfg = Arc::new(config);
TlsClient {
socket: sock,
closing: false,
tls_session: rustls::client::ClientSession::new(&config, "thing.com")
tls_session: rustls::client::ClientSession::new(&cfg, "testserver.com")
}
}

View File

@ -7,6 +7,7 @@ use msgs::handshake::{ClientExtension};
use msgs::deframer::MessageDeframer;
use msgs::message::Message;
use client_hs;
use verifycert;
use handshake::HandshakeError;
use rand;
@ -17,20 +18,25 @@ use std::collections::VecDeque;
pub struct ClientConfig {
/* List of ciphersuites, in preference order. */
pub ciphersuites: Vec<&'static SupportedCipherSuite>
pub ciphersuites: Vec<&'static SupportedCipherSuite>,
/* Collection of root certificates. */
pub root_store: verifycert::RootCertStore
}
impl ClientConfig {
pub fn default() -> ClientConfig {
ClientConfig {
ciphersuites: DEFAULT_CIPHERSUITES.to_vec(),
root_store: verifycert::RootCertStore::empty()
}
}
}
pub struct ClientHandshakeData {
pub server_cert_chain: Option<CertificatePayload>,
pub server_cert_chain: CertificatePayload,
pub ciphersuite: Option<&'static SupportedCipherSuite>,
pub dns_name: String,
pub client_random: Vec<u8>,
pub server_random: Vec<u8>,
pub kx_data: KeyExchangeData,
@ -38,10 +44,11 @@ pub struct ClientHandshakeData {
}
impl ClientHandshakeData {
fn new() -> ClientHandshakeData {
fn new(host_name: &str) -> ClientHandshakeData {
ClientHandshakeData {
server_cert_chain: None,
server_cert_chain: Vec::new(),
ciphersuite: None,
dns_name: host_name.to_string(),
client_random: Vec::new(),
server_random: Vec::new(),
kx_data: KeyExchangeData::Invalid,
@ -78,7 +85,7 @@ impl ClientSession {
hostname: &str) -> ClientSession {
let mut cs = ClientSession {
config: client_config.clone(),
handshake_data: ClientHandshakeData::new(),
handshake_data: ClientHandshakeData::new(hostname),
secrets_current: SessionSecrets::for_client(),
message_deframer: MessageDeframer::new(),
tls_queue: VecDeque::new(),

View File

@ -10,6 +10,7 @@ use msgs::handshake::{EllipticCurveList, SupportedCurves};
use msgs::handshake::{ECPointFormatList, SupportedPointFormats};
use client::{ClientSession, ConnState};
use suites;
use verifycert;
use handshake::{HandshakeError, Expectation, ExpectFunction};
macro_rules! extract_handshake(
@ -111,9 +112,8 @@ fn ExpectCertificate_expect() -> Expectation {
fn ExpectCertificate_handle(sess: &mut ClientSession, m: &Message) -> Result<ConnState, HandshakeError> {
let cert_chain = extract_handshake!(m, HandshakePayload::Certificate).unwrap();
sess.handshake_data.server_cert_chain = Some(cert_chain.clone());
sess.handshake_data.server_cert_chain = cert_chain.clone();
println!("we have server cert {:?}", cert_chain);
/* TODO: verify cert here, extract subject pubkey */
Ok(ConnState::ExpectServerKX)
}
@ -137,7 +137,10 @@ fn ExpectServerKX_handle(sess: &mut ClientSession, m: &Message) -> Result<ConnSt
return Err(HandshakeError::General("cannot decode server's kx".to_string()));
}
println!("we have serverkx {:?}", maybe_decoded_kx);
let decoded_kx = maybe_decoded_kx.unwrap();
println!("we have serverkx {:?}", decoded_kx);
/* TODO: check signature by subject pubkey on this struct */
Ok(ConnState::ExpectServerHelloDone)
}
@ -156,6 +159,12 @@ fn ExpectServerHelloDone_expect() -> Expectation {
fn ExpectServerHelloDone_handle(sess: &mut ClientSession, m: &Message) -> Result<ConnState, HandshakeError> {
println!("we have serverhellodone");
let rc = verifycert::verify_cert(&sess.config.root_store,
&sess.handshake_data.server_cert_chain,
&sess.handshake_data.dns_name);
println!("verify result {:?}", rc);
Ok(ConnState::ExpectCCS)
}

View File

@ -2,11 +2,14 @@ use msgs::enums::{ContentType, HandshakeType};
use msgs::message::{Message, MessagePayload};
use std::fmt::Debug;
extern crate webpki;
#[derive(Debug)]
pub enum HandshakeError {
InappropriateMessage { expect_types: Vec<ContentType>, got_type: ContentType },
InappropriateHandshakeMessage { expect_types: Vec<HandshakeType>, got_type: HandshakeType },
NoCertificatesPresented,
WebPKIError(webpki::Error),
General(String)
}

View File

@ -4,6 +4,7 @@ use msgs::message::Message;
mod rand;
mod session;
mod verifycert;
pub mod server;
pub mod client;
mod handshake;

View File

@ -2,7 +2,7 @@ use msgs::enums::{ProtocolVersion, HandshakeType};
use msgs::enums::{CipherSuite, Compression, ExtensionType, ECPointFormat, NamedCurve};
use msgs::enums::{HashAlgorithm, SignatureAlgorithm, HeartbeatMode, ServerNameType};
use msgs::enums::{ECCurveType};
use msgs::base::{Payload, PayloadU8, PayloadU24};
use msgs::base::{Payload, PayloadU8, PayloadU16, PayloadU24};
use msgs::codec;
use msgs::codec::{Codec, Reader};
@ -570,6 +570,26 @@ impl Codec for ECParameters {
}
}
#[derive(Debug)]
pub struct DigitallySignedStruct {
pub alg: SignatureAndHashAlgorithm,
pub sig: PayloadU16
}
impl Codec for DigitallySignedStruct {
fn encode(&self, bytes: &mut Vec<u8>) {
self.alg.encode(bytes);
self.sig.encode(bytes);
}
fn read(r: &mut Reader) -> Option<DigitallySignedStruct> {
let alg = try_ret!(SignatureAndHashAlgorithm::read(r));
let sig = try_ret!(PayloadU16::read(r));
Some(DigitallySignedStruct { alg: alg, sig: sig })
}
}
#[derive(Debug)]
pub struct ServerECDHParams {
pub curve_params: ECParameters,
@ -593,21 +613,20 @@ impl Codec for ServerECDHParams {
#[derive(Debug)]
pub struct ECDHEServerKeyExchange {
pub params: ServerECDHParams,
pub sig: Vec<u8>
pub dss: DigitallySignedStruct
}
impl Codec for ECDHEServerKeyExchange {
fn encode(&self, bytes: &mut Vec<u8>) {
self.params.encode(bytes);
bytes.extend(self.sig.iter());
self.dss.encode(bytes);
}
fn read(r: &mut Reader) -> Option<ECDHEServerKeyExchange> {
let params = try_ret!(ServerECDHParams::read(r));
let mut sig = Vec::new();
sig.extend_from_slice(r.rest());
let dss = try_ret!(DigitallySignedStruct::read(r));
Some(ECDHEServerKeyExchange { params: params, sig: sig })
Some(ECDHEServerKeyExchange { params: params, dss: dss })
}
}

102
src/verifycert.rs Normal file
View File

@ -0,0 +1,102 @@
extern crate webpki;
extern crate ring;
extern crate time;
use self::ring::input::Input;
use msgs::handshake::ASN1Cert;
use handshake::HandshakeError;
/* Which signature verification mechanisms we support. No particular
* order. */
static SUPPORTED_SIG_ALGS: &'static [&'static webpki::SignatureAlgorithm] = &[
&webpki::ECDSA_P256_SHA1,
&webpki::ECDSA_P256_SHA256,
&webpki::ECDSA_P256_SHA384,
&webpki::ECDSA_P256_SHA512,
&webpki::ECDSA_P384_SHA1,
&webpki::ECDSA_P384_SHA256,
&webpki::ECDSA_P384_SHA384,
&webpki::ECDSA_P384_SHA512,
&webpki::RSA_PKCS1_2048_8192_SHA1,
&webpki::RSA_PKCS1_2048_8192_SHA256,
&webpki::RSA_PKCS1_2048_8192_SHA384,
&webpki::RSA_PKCS1_2048_8192_SHA512,
&webpki::RSA_PKCS1_3072_8192_SHA384
];
/* This is like a webpki::TrustAnchor, except it owns
* rather than borrows its memory. That prevents lifetimes
* leaking up the object tree. */
struct OwnedTrustAnchor {
subject: Vec<u8>,
spki: Vec<u8>,
name_constraints: Option<Vec<u8>>
}
impl OwnedTrustAnchor {
fn from_trust_anchor(t: &webpki::TrustAnchor) -> OwnedTrustAnchor {
OwnedTrustAnchor {
subject: t.subject.to_vec(),
spki: t.spki.to_vec(),
name_constraints: t.name_constraints.map(|x| x.to_vec())
}
}
fn to_trust_anchor(&self) -> webpki::TrustAnchor {
webpki::TrustAnchor {
subject: &self.subject,
spki: &self.spki,
name_constraints: self.name_constraints.as_ref().map(|x| x.as_slice())
}
}
}
pub struct RootCertStore {
roots: Vec<OwnedTrustAnchor>
}
impl RootCertStore {
pub fn empty() -> RootCertStore {
RootCertStore { roots: Vec::new() }
}
pub fn add(&mut self, der: &[u8]) -> Result<(), webpki::Error> {
let ta = try!(
webpki::trust_anchor_util::cert_der_as_trust_anchor(Input::new(der).unwrap())
);
let ota = OwnedTrustAnchor::from_trust_anchor(&ta);
self.roots.push(ota);
Ok(())
}
}
pub fn verify_cert(roots: &RootCertStore,
presented_certs: &Vec<ASN1Cert>,
dns_name: &str) -> Result<(), HandshakeError> {
if presented_certs.len() == 0 {
return Err(HandshakeError::NoCertificatesPresented);
}
/* EE cert must appear first. */
let ee = Input::new(&presented_certs[0].body).unwrap();
let chain: Vec<Input> = presented_certs.iter()
.skip(1)
.map(|cert| Input::new(&cert.body).unwrap())
.collect();
let trustroots: Vec<webpki::TrustAnchor> = roots.roots.iter()
.map(|x| x.to_trust_anchor())
.collect();
webpki::verify_tls_cert(&SUPPORTED_SIG_ALGS,
&trustroots,
&chain,
ee,
time::get_time())
.and_then(|_| webpki::verify_cert_dns_name(ee,
Input::new(dns_name.as_bytes()).unwrap()))
.map_err(|err| HandshakeError::WebPKIError(err))
}

View File

@ -1,6 +1,6 @@
#!/bin/sh
openssl s_server -www -accept 8443 -key test/key.pem -cert test/cert.pem -msg -debug -state &
openssl s_server -www -accept 8443 -key test/end.key -cert test/end.cert -CAfile test/end.chain -msg -debug -state &
server=$!
sleep 1

5
test/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.chain
*.cert
*.key
*.req
*.der

View File

@ -7,3 +7,6 @@ openssl req -nodes -newkey rsa:3072 -keyout inter.key -out inter.req -sha256 -ba
openssl req -nodes -newkey rsa:2048 -keyout end.key -out end.req -sha256 -batch -subj "/CN=testserver.com"
openssl x509 -req -in inter.req -out inter.cert -CA ca.cert -CAkey ca.key -sha256 -set_serial 123 -extensions v3_inter -extfile openssl.cnf
openssl x509 -req -in end.req -out end.cert -CA inter.cert -CAkey inter.key -sha256 -set_serial 456 -extensions v3_end -extfile openssl.cnf
cat inter.cert ca.cert > end.chain
openssl asn1parse -in ca.cert -out ca.der > /dev/null

View File

@ -4,6 +4,7 @@ basicConstraints = critical,CA:false
keyUsage = nonRepudiation, digitalSignature
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
subjectAltName = @alt_names
[ v3_inter ]
subjectKeyIdentifier = hash
@ -11,3 +12,7 @@ extendedKeyUsage = critical,serverAuth, clientAuth
basicConstraints = CA:true
keyUsage = cRLSign, keyCertSign, digitalSignature, nonRepudiation,keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign
[ alt_names ]
DNS.1 = testserver.com
DNS.2 = second.testserver.com