Skip to content

Commit

Permalink
Command line utility
Browse files Browse the repository at this point in the history
For now it has two subcommands:
* just parse end entity certificate
* verify server certificate

Is not meant to be public, does not provide a stable interface.

Useful mostly for debugging of WebPKI itself.

```
% cargo run -p webpki-bin -- print-cert ./tests/netflix/ca.der
...
target/debug/webpki: failed to parse a cert ./tests/netflix/ca.der: BadDER
```

```
% cargo run -p webpki-bin -- verify-server-cert \
    --server-cert tests/netflix/ee.der \
    --trusted-root tests/netflix/ca.der \
    --intermediates tests/netflix/inter.der \
    --time 1492441716
...
server certificate tests/netflix/ee.der is valid
```

I agree to license my contributions to each file under the terms
given at the top of each file I changed.
  • Loading branch information
stepancheg committed Feb 17, 2021
1 parent 082a744 commit 429f549
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@ rpath = false
lto = true
debug-assertions = false
codegen-units = 1

[workspace]
members = [
".",
"webpki-bin",
]
27 changes: 27 additions & 0 deletions webpki-bin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2021 Brian Smith.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

[package]
authors = ["Brian Smith <[email protected]>"]
description = "Test binaries for webpki crate."
edition = "2018"
license-file = "../LICENSE"
name = "webpki-bin"
repository = "https://github.com/briansmith/webpki"
version = "0.0.0"
publish = false

[dependencies]
webpki = { version = "*", path = "..", features = ["std"] }
clap = "3.0.0-beta.2"
138 changes: 138 additions & 0 deletions webpki-bin/src/bin/webpki.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use clap::Clap;
use std::convert::TryFrom;
use std::env;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process;
use std::time::SystemTime;

#[derive(Clap)]
#[clap(about = "Parse and print a certificate")]
struct PrintCert {
filename: PathBuf,
}

#[derive(Clap)]
#[clap(about = "Verify server certificate")]
struct VerifyServerCert {
#[clap(long)]
trusted_root: PathBuf,
#[clap(long)]
intermediates: Vec<PathBuf>,
#[clap(long)]
server_cert: PathBuf,
#[clap(long, about = "Seconds since epoch")]
time: Option<u64>,
}

#[derive(Clap)]
enum Command {
PrintCert(PrintCert),
VerifyServerCert(VerifyServerCert),
}

#[derive(Clap)]
#[clap(about = "Utility to debug webpki. Does not have a stable API.")]
struct Opts {
#[clap(subcommand)]
command: Command,
}

fn print_error_and_exit(s: String) -> ! {
let arg0 = env::args().next();
let arg0 = arg0.as_ref().map(|s| s.as_str()).unwrap_or("webpki");
eprintln!("{}: {}", arg0, s);
process::exit(1)
}

fn read_file(path: &Path) -> Vec<u8> {
match fs::read(path) {
Ok(bytes) => bytes,
Err(e) => print_error_and_exit(format!("could not read file {}: {}", path.display(), e)),
}
}

fn parse_end_entity_cert<'a>(der: &'a [u8], path: &Path) -> webpki::EndEntityCert<'a> {
match webpki::EndEntityCert::try_from(der) {
Ok(cert) => cert,
Err(e) => {
print_error_and_exit(format!("failed to parse a cert {}: {}", &path.display(), e))
}
}
}

fn print_cert(args: PrintCert) {
let der = read_file(&args.filename);

let _cert = parse_end_entity_cert(&der, &args.filename);

eprintln!("cert {} parsed successfully", &args.filename.display());
// TODO: actually print something about the certificate
}

static ALL_SIGALGS: &[&webpki::SignatureAlgorithm] = &[
&webpki::ECDSA_P256_SHA256,
&webpki::ECDSA_P256_SHA384,
&webpki::ECDSA_P384_SHA256,
&webpki::ECDSA_P384_SHA384,
&webpki::ED25519,
&webpki::RSA_PKCS1_2048_8192_SHA256,
&webpki::RSA_PKCS1_2048_8192_SHA384,
&webpki::RSA_PKCS1_2048_8192_SHA512,
&webpki::RSA_PKCS1_3072_8192_SHA384,
];

fn verify_server_cert(args: VerifyServerCert) {
let server_cert = read_file(&args.server_cert);
let server_cert = parse_end_entity_cert(&server_cert, &args.server_cert);

let trusted_root = read_file(&args.trusted_root);

let trust_anchor = match webpki::TrustAnchor::from_cert_der(&trusted_root) {
Ok(trust_anchor) => trust_anchor,
Err(e) => print_error_and_exit(format!(
"failed to parse trust anchor from {}: {}",
args.trusted_root.display(),
e
)),
};

let intermediates: Vec<Vec<u8>> = args
.intermediates
.iter()
.map(|intermediate| read_file(intermediate))
.collect();
let intermediates: Vec<&[u8]> = intermediates
.iter()
.map(|intermediate| intermediate.as_slice())
.collect();

let time = args.time.unwrap_or_else(|| {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
});
let time = webpki::Time::from_seconds_since_unix_epoch(time);

match server_cert.verify_is_valid_tls_server_cert(
ALL_SIGALGS,
&webpki::TLSServerTrustAnchors(&[trust_anchor]),
&intermediates,
time,
) {
Ok(()) => {}
Err(e) => print_error_and_exit(format!("verify server certificate failed: {}", e)),
}

eprintln!("server certificate {} is valid", args.server_cert.display());
}

fn main() {
let opts: Opts = Opts::parse();
match opts.command {
Command::PrintCert(args) => print_cert(args),
Command::VerifyServerCert(args) => verify_server_cert(args),
}
}

0 comments on commit 429f549

Please sign in to comment.