mirror of https://github.com/ctz/rustls
verify certs with webpki
This commit is contained in:
parent
9ac839a44b
commit
2a9dd6f801
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use msgs::message::Message;
|
|||
|
||||
mod rand;
|
||||
mod session;
|
||||
mod verifycert;
|
||||
pub mod server;
|
||||
pub mod client;
|
||||
mod handshake;
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
*.chain
|
||||
*.cert
|
||||
*.key
|
||||
*.req
|
||||
*.der
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue