Decode a certificate
Tags: rust, x509
Published: 2020-06-22 20:40:55

---
title: Decode a certificate
series: "TDD and Rust: A CLI for decoding certs"
published: true 
description: Decoding and printing certificates
tags: rust,x509
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/q7c4fzatzx2432tde6nc.jpeg 
---

# Introduction
In this post I will leave a note at the end of some sections linking to the latest code up to that point. It will look like this:

> :link: [wayofthepie/cert-decoder@a9d761e](https://github.com/wayofthepie/cert-decoder/tree/a9d761e1ba306587a332e97e9fd4e654f1049ab9)

That link points to the latest code from the last post. 

Table of Contents
=================

   * [Some missing things](#some-missing-things)
   * [Read a certificate](#read-a-certificate)
      * [x509-parser API](#x509parser-api)
   * [Validate the file is a certificate](#validate-the-file-is-a-certificate)
      * [Error handling](#error-handling)
      * [Refactor](#refactor)
      * [Parse the der encoded cert](#parse-the-der-encoded-cert)
      * [Print the certificate](#print-the-certificate)
   * [Tiny refactor](#tiny-refactor)
   * [Conclusion](#conclusion)


# Some missing things
After publishing the first post I realized I missed a positive test, a test which checks that everything went ok. Let's write that. 

```rust
#[test]
fn should_succeed() {
    let args = vec!["a-file".to_owned()];
    let validator = FakeValidator { is_file: true };
    let result = execute(validator, args);
    assert!(result.is_ok());
}
```
It's a good idea to see it fail first. Let's change a behaviour it expects by making it return an error if the file _does_ exist:

```diff
- if !validator.is_file(path) {
+ if validator.is_file(path) {
   return Err(
...
```
Now it will fail:

```bash
✦ ➜ cargo -q test should_succeed

running 1 test
F
failures:

---- test::should_succeed stdout ----
thread 'test::should_succeed' panicked at 'assertion failed: result.is_ok()', src/main.rs:98:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test::should_succeed

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 2 filtered out

error: test failed, to rerun pass '--bin cert-decode'
```
    
And if we revert our change:

```diff
- if validator.is_file(path) {
+ if !validator.is_file(path) {
...    
```
It will pass:

```bash
✦ ➜ cargo -q test should_succeed

running 1 test
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
```
Great! Now for some new things.

> :link: [wayofthepie/cert-decoder@66da25a](https://github.com/wayofthepie/cert-decoder/tree/66da25ac49840efe43d4913eb178e71df52f5997)
    
# Read a certificate
Time to read a certificate. The [x509-parser](https://docs.rs/crate/x509-parser/0.7.0) crate will allow us to do this. Add as a dependency to `Cargo.toml`.

```toml
[package]
name = "cert-decode"
version = "0.1.0"
authors = ["Stephen OBrien <wayofthepie@users.noreply.github.com>"]
edition = "2018"

[dependencies]
x509-parser = "0.7.0"
```

> **:memo: Note**
>
> I use [cargo-edit](https://github.com/killercup/cargo-edit) to update `Cargo.toml`. You can install it by running:
>
> ```bash
> ✦ ➜ cargo install cargo-edit
> ```
>
> Then to add a dependency you just need to run:
> ```bash
> ✦ ➜ cargo add x509-parser
>     Updating 'https://github.com/rust-lang/crates.io-index' index
>       Adding x509-parser v0.7.0 to dependencies
> ```
> See [the README](https://github.com/killercup/cargo-edit/blob/master/README.md) for more details. The code blocks in this note have
> incorrect formatting, they are centered. I raised a bug about this, see [thepracticaldev/dev.to#8767](https://github.com/thepracticaldev/dev.to/issues/8767).


It's a good idea to rebuild when adding a new dependency.

```rust
✦ ➜ cargo build
    Updating crates.io index
   Compiling autocfg v1.0.0
   Compiling bitflags v1.2.1
   Compiling ryu v1.0.5
   Compiling lexical-core v0.7.4
   Compiling memchr v2.3.3
   Compiling version_check v0.9.2
   Compiling arrayvec v0.5.1
   Compiling static_assertions v1.1.0
   Compiling cfg-if v0.1.10
   Compiling libc v0.2.71
   Compiling base64 v0.11.0
   Compiling nom v5.1.2
   Compiling num-traits v0.2.12
   Compiling num-integer v0.1.43
   Compiling num-bigint v0.2.6
   Compiling time v01.3
   Compiling rusticata-macros v2.1.0
   Compiling der-parser v3.0.4
   Compiling x509-parser v0.7.0
   Compiling cert-decode v0.1.0 (/home/chaospie/repos/blog-cert-decode/cert-decode)
    Finished dev [unoptimize + debuginfo] target(s) in 8.70s
```
The `x509-parser` crate has pulled in a bunch of transitive dependencies. Not too many though. When working on a larger project you may want to view the overall dependency hierarchy. You can do this with `cargo tree`. For example:

```bash
✦ ➜ cargo tree
cert-decode v0.1.0 (/home/chaospie/repos/blog-cert-decode/cert-decode)
└── x509-parser v0.7.0
    ├── base64 v0.11.0
    ├── der-parser v3.0.4
    │   ├── nom v5.1.2
    │   │   ├── lexical-core v0.7.4
    │   │   │   ├── arrayvec v0.5.1
    │   │   │   ├── bitflags v1.2.1
    │   │   │   ├── cfg-if v0.1.10
    │   │   │   ├── ryu v1.0.5
    │   │   │   └── static_assertions v1.1.0
    │   │   └── memchr v2.3.3
    │   │   [build-dependencies]
    │   │   └── version_check v0.9.2
    │   ├── num-bigint v0.2.6
    │   │   ├── num-integer v0.1.43
    │   │   │   └── num-traits v0.2.12
    │   │   │       [build-dependencies]
    │   │   │       └── autocfg v1.0.0
    │   │   │   [build-dependencies]
    │   │   │   └── autocfg v1.0.0
    │   │   └── num-traits v0.2.12 (*)
    │   │   [build-dependencies]
    │   │   └── autocfg v1.0.0
    │   └── rusticata-macros v2.1.0
    │       └── nom v5.1.2 (*)
    ├── nom v5.1.2 (*)
    ├── num-bigint v0.2.6 (*)
    ├── rusticata-macros v2.1.0 (*)
    └── time v0.1.43
        └── libc v0.2.71
```

> **:memo: Note**
> 
> As of Rust 1.44.0 `cargo tree` is part of `cargo` if you are using a version before that you will need to install [cargo-tree](https://crates.io/crates/cargo-tree). You should update to the latest Rust if there is no reason to be on a version less than 1.44.0.

I've used the `x509-parser` crate in the past so I know a bit about it's API. But let's do some exploration anyway. First, let's set a goal and make a tiny test list. Our goal is simply to be able to print certificate details. Up to now, we have verified the argument we pass is a file, so I think what we should do next is:

---
**Read and print certificate**  
- ☐ Validate file is a certificate
- ☐ Print the certificate

---
Let's dive into the docs for `x509-parser`.

## x509-parser API
All crates published to [crates.io](https://crates.io/) should have docs on [docs.rs](https://docs.rs). These docs will contain the public API of the crate and whatever further documentation the author added. We're using version 0.7.0 of the `x509-parser` crate, the docs for this are [here](https://docs.rs/x509-parser/0.7.0/x509_parser/). The very first example in these docs does almost what we need:

```rust
use x509_parser::parse_x509_der;

static IGCA_DER: &'static [u8] = include_bytes!("../assets/IGC_A.der");

let res = parse_x509_der(IGCA_DER);
match res {
    Ok((rem, cert)) => {
        assert!(rem.is_empty());
        //
        assert_eq!(cert.tbs_certificate.version, 2);
    },
    _ => panic!("x509 parsing failed: {:?}", res),
}
```

It seems we could use the [parse_x509_der](https://docs.rs/x509-parser/0.7.0/x509_parser/fn.parse_x509_der.html) function to parse our certificate. Our certificate should be in PEM format however, that was a constraint we set in the initial post. Is there anything that can deal directly with PEM certificates in this API? 

There is! The [x509_parser::pem](https://docs.rs/x509-parser/0.7.0/x509_parser/pem/index.html) module has functionality for doing just this. The second example [in that modules docs](https://docs.rs/x509-parser/0.7.0/x509_parser/pem/index.html) does just what we want, it uses the [pem_to_der](https://docs.rs/x509-parser/0.7.0/x509_parser/pem/fn.pem_to_der.html) function to convert a PEM encoded certificate into DER (Distinguished Encoding Rules) and then calls [parse_x509_der](https://docs.rs/x509-parser/0.7.0/x509_parser/fn.parse_x509_der.html) on that DER to build a [X509Certificate](https://docs.rs/x509-parser/0.7.0/x509_parser/x509/struct.X509Certificate.html). Here is the example:

```rust
use x509_parser::pem::pem_to_der;
use x509_parser::parse_x509_der;

static IGCA_PEM: &'static [u8] = include_bytes!("../assets/IGC_A.pem");

let res = pem_to_der(IGCA_PEM);
match res {
    Ok((rem, pem)) => {
        assert!(rem.is_empty());
        //
        assert_eq!(pem.label, String::from("CERTIFICATE"));
        //
        let res_x509 = parse_x509_der(&pem.contents);
        assert!(res_x509.is_ok());
    },
    _ => panic!("PEM parsing failed: {:?}", res),
}
```
Don't worry if you don't understand everything in this example, it's not too important for this post. Let's look closer at [pem_to_der](https://docs.rs/x509-parser/0.7.0/x509_parser/pem/fn.pem_to_der.html).

```rust
pub fn pem_to_der<'a>(i: &'a [u8]) -> IResult<&'a [u8], Pem, PEMError>
```

It takes a `&'a [u8]`, a [slice](https://doc.rust-lang.org/std/primitive.slice.html) of bytes with a lifetime[^generics-lifetimes] of `'a`, and returns `IResult<&'a [u8], Pem, PEMError>`. In short lifetimes tell the compiler how long a reference lives. In this specific case, the slice `i` which the function takes as an argument must live as long as the slice returned in the `IResult` return type, as both have a lifetime of `'a`. This tells us the slice in the return type must either be `i` or a subslice of `i`. For more information see [Generic Types, Traits, and Lifetimes](https://doc.rust-lang.org/book/ch10-00-generics.html?highlight=generics#generic-types-traits-and-lifetimes).

Glossing over some other details which are outside the scope of this post, this `IResult` is effectively just a [Result](https://doc.rust-lang.org/std/result/enum.Result.html) type which we saw in the first post. It can return `Ok` with some value or `Err` with an error. In this case, the type of the value in `Err` will be [PEMError](https://docs.rs/x509-parser/0.7.0/x509_parser/error/enum.PEMError.html). 

 
# Validate the file is a certificate
Now we have enough knowledge to write a test in the case our cert is not PEM encoded. First, let's get a cert and save it so we can use that in our tests.
```bash
➜ openssl s_client -connect google.com:443 2>/dev/null < /dev/null \
>     | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > google.com.crt

➜ cat google.com.crt
-----BEGIN CERTIFICATE-----
MIIJTzCCCDegAwIBAgIQVvrczQ6+8BwIAAAAAENV5zANBgkqhkiG9w0BAQsFADBC
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMRMw
...
d5JOd+lJOypPGs0/p5OrR8B84Y7wyKFD/EXaKYVMZ4RUXnoAi5DF5RLKNAmnt7R9
V6z8Kz2boaY5oZ0gvrA49R6T+u3yrstte931N49lwpaVsoA=
-----END CERTIFICATE-----
```
I've stored this cert in the repo [here](https://github.com/wayofthepie/cert-decoder/blob/be75e2c756984c289768788d3f1ffadaa223ddc2/resources/google.com.crt). The test is as follows:

```rust 
#[test]
fn should_error_if_given_argument_is_not_a_pem_encoded_certificate() {
    let args = vec!["real-cert".to_owned()];
    let validator = FakeValidator { is_file: true };
    let result = execute(validator, args);
    assert!(result.is_err())
}
```

The update to the `execute` function will need a bit of refactoring, but first, let's implement it in the simplest way possible.

```rust
fn execute(validator: impl PathValidator, args: Vec<String>) -> Result<(), String> {
    if args.len() != 1 {
        let error = format!(
            "{}{}",
            "Error: did not receive a single argument, ",
            "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
        );
        return Err(error);
    }
    let path = &args[0];
    if !validator.is_file(path) {
        return Err(
            "Error: path given is not a regular file, please update to point to a certificate."
                .to_owned(),
        );
    }
    // read file to string
    let cert = std::fs::read_to_string(path).unwrap();
    // pem to der
    let _ = pem_to_der(cert.as_bytes()).unwrap();
    Ok(())
}
```
We use [std::fs::read_to_string](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) to read the file path we pass as an argument directly to a string. This call returns a `Result` as it can fail if the path does not exist. But we know it does exist at this point, so we just [unwrap](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap) the value, giving us our cert as a string. Then we pass that string as bytes, by calling the [as_bytes](https://doc.rust-lang.org/std/string/struct.String.html#method.as_bytes) function on it, to `pem_to_der`. This can fail and because here we just call `unwrap` this will [panic](https://doc.rust-lang.org/std/macro.panic.html) if `pem_to_der` returns an `Err` value instead of and `Ok` value.

To see what I mean, update the test so it reads `Cargo.toml`.
```rust 
#[test]
fn should_error_if_given_argument_is_not_a_pem_encoded_certificate() {
    let args = vec!["Cargo.toml".to_owned()];
    let validator = FakeValidator { is_file: true };
    let result = execute(validator, args);
    assert!(result.is_err())
}
```

It will fail as follows because `Cargo.toml` is not PEM encoded:

```rust 
➜ cargo -q test pem

running 1 test
F
failures:

---- test::should_error_if_given_argument_is_not_a_pem_encoded_certificate stdout ----
thread 'test::should_error_if_given_argument_is_not_a_pem_encoded_certificate' panicked at 'called `Result::unwrap()` on an `Err` value: Error(MissingHeader)', src/main.rs:33:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test::should_error_if_given_argument_is_not_a_pem_encoded_certificate

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 3 filtered out

error: test failed, to rerun pass '--bin cert-decode'
```
Even though it did error, it didn't do so in a way we could handle in our test. It would be better to _not_ call unwrap on the return of `pem_to_der`. To do so, we need to change the return type of `execute` so it allows us to return both our existing `String` errors and the `PEMError` which `pem_to_der` returns.

## Error handling
There are many different ways to handle errors in Rust. A lot is going on in this space currently in regards libaries and discussions in the language itself. How you handle errors in a library vs in an application can vary wildly too. I don't want to add any more dependencies here and I also want to keep this simple, so we'll use the most general type for handling errors, `Box<dyn std::error::Error>`[^error-handling]. 

[Box](https://doc.rust-lang.org/beta/std/boxed/index.html) is a simple way of allocating something on the heap in Rust. `Box<dyn std::error::Error>` is a trait object[^trait-objects], this allows us to return a value of any type that implements the [std::error::Error](https://doc.rust-lang.org/std/error/trait.Error.html) trait.

Let's refactor. First, update `execute` as follows.

```diff
-fn execute(validator: impl PathValidator, args: Vec<String>) -> Result<(), String> {
+fn execute(
+    validator: impl PathValidator,
+    args: Vec<String>,
+) -> Result<(), Box<dyn std::error::Error>> {
     if args.len() != 1 {
         let error = format!(
             "{}{}",
             "Error: did not receive a single argument, ",
             "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
         );
-        return Err(error);
+        return Err(error.into());
     }
     let path = &args[0];
     if !validator.is_file(path) {
         return Err(
             "Error: path given is not a regular file, please update to point to a certificate."
-                .to_owned(),
+                .into(),
         );
     }
     let cert = std::fs::read_to_string(path).unwrap();
-    let _ = pem_to_der(cert.as_bytes()).unwrap();
+    let _ = pem_to_der(cert.as_bytes())?;
     Ok(())
 }
```
We change the return type to of `execute` to `Result<(), Box<dyn std::error::Error>>`. We were previously returning `String`'s from our custom errors, we can call `into` on our strings and this will convert them into `Box<dyn std::error::Error>`. There is an instance of [From](https://doc.rust-lang.org/std/convert/trait.From.html) for converting a [String to a Box\<dyn std::error::Error\>](https://doc.rust-lang.org/std/convert/trait.From.html#impl-From%3CString%3E-2), because of this we get an [Into](https://doc.rust-lang.org/std/convert/trait.Into.html) instance for automatically.

Finally, we add a `?`[^question-mark] to immediately return the error if `pem_to_der` returns an error. Next update `main`.

```diff
-fn main() -> Result<(), String> {
+fn main() -> Result<(), Box<dyn std::error::Error>> {
     let args = std::env::args().skip(1).collect();
     let validator = CertValidator;
     execute(validator, args)
 }
```
We just change the return type here. Finally, update the tests.
```diff
 #[cfg(test)]
 mod test {
     ... 
     #[test]
     fn should_error_if_not_given_a_single_argument() {
         ...
         assert!(result.is_err());
         assert_eq!(
-            result.err().unwrap(),
+            format!("{}", result.err().unwrap()),
             format!(
                 "{}{}",
                 "Error: did not receive a single argument, ",
                 "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
             )
         );
     }
 
     #[test]
     fn should_error_if_argument_is_not_a_regular_file() {
         ...
         assert!(result.is_err());
         assert_eq!(
-            result.err().unwrap(),
+            format!("{}", result.err().unwrap()),
             "Error: path given is not a regular file, please update to point to a certificate."
         );
     }
 
     #[test]
     fn should_error_if_given_argument_is_not_a_pem_encoded_certificate() {
        ...
     }
 
     #[test]
     fn should_succeed() {
-        let args = vec!["a-file".to_owned()];
+        let args = vec!["resources/google.com.crt".to_owned()];
         let validator = FakeValidator { is_file: true };
         let result = execute(validator, args);
         assert!(result.is_ok());
     }
 
```
We call [format](https://doc.rust-lang.org/std/macro.format.html) on the error message to turn the `Box<dyn std::error::Error>` in to a string. We also change the `should_succeed` test to read the real cert. This does IO, but that's ok for now. Re-run and the tests should be green.

```bash
✦ ➜ cargo -q test

running 4 tests
....
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
> :link: [wayofthepie/cert-decoder@37cbc87](https://github.com/wayofthepie/cert-decoder/tree/37cbc87df3ef54f9a72a7be468e20cc26d7011d1)

## Refactor
Now we have refactored to allow returning different types of errors, read, and decoded the PEM certificate into DER format. Let's clean things up a little. We are doing IO again, so let's tackle that first. Right now we are passing an implementation of `PathValidator` to `execute`. It would make sense to expand what this trait does, but we should rename it. Let's call it `FileProcessor`. Implementations will have `is_file` and `read_to_string` so this makes sense. Let's also rename `CertValidator` to `CertProcessor`. 

```diff
 use std::path::Path;
 use x509_parser::pem::pem_to_der;
 
-trait PathValidator {
+trait FileProcessor {
     fn is_file(&self, path: &str) -> bool;
 }
 
-struct CertValidator;
+struct CertProcessor;
 
-impl PathValidator for CertValidator {
+impl FileProcessor for CertProcessor {
     fn is_file(&self, path: &str) -> bool {
         Path::new(path).is_file()
     }
 }
 
 fn execute(
-    validator: impl PathValidator,
+    validator: impl FileProcessor,
     args: Vec<String>,
 ) -> Result<(), Box<dyn std::error::Error>> {
    ...
    Ok(())
 }
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let args = std::env::args().skip(1).collect();
-    let validator = CertValidator;
+    let validator = CertProcessor;
     execute(validator, args)
 }
 
 #[cfg(test)]
 mod test {
 
-    use crate::{execute, PathValidator};
+    use crate::{execute, FileProcessor};
 
-    struct FakeValidator {
+    struct FakeProcessor {
         is_file: bool,
     }
 
-    impl PathValidator for FakeValidator {
+    impl FileProcessor for FakeProcessor {
         fn is_file(&self, _: &str) -> bool {
             self.is_file
         }
     }
 
     #[test]
     fn should_error_if_not_given_a_single_argument() {
         // arrange
         let args = Vec::new();
-        let validator = FakeValidator { is_file: false };
+        let validator = FakeProcessor { is_file: false };
 
         ...
     }
 
     #[test]
     fn should_error_if_argument_is_not_a_regular_file() {
         // arrange
         let args = vec!["not-a-regular-file".to_owned()];
-        let validator = FakeValidator { is_file: false };
+        let validator = FakeProcessor { is_file: false };
 
         ...
     }
 
     #[test]
     fn should_error_if_given_argument_is_not_a_pem_encoded_certificate() {
         let args = vec!["Cargo.toml".to_owned()];
-        let validator = FakeValidator { is_file: true };
+        let validator = FakeProcessor { is_file: true };
         let result = execute(validator, args);
         assert!(result.is_err())
     }
 
     #[test]
     fn should_succeed() {
         let args = vec!["resources/google.com.crt".to_owned()];
-        let validator = FakeValidator { is_file: true };
+        let validator = FakeProcessor { is_file: true };
         let result = execute(validator, args);
         assert!(result.is_ok());
     }
 }
```

> **:memo: Note**
> You may have noticed I forgot to update the name of the variables from `validator` to something more appropriate like `processor`!
> This was indeed a mistake. I added a small refactor section near the end of the post which fixes this.
>
> :link: [wayofthepie/cert-decoder@a30ae7c](https://github.com/wayofthepie/cert-decoder/tree/a30ae7cf5cfdb51ad1f8cea79e0b20e9ed2f41b3)

Now we can add a `read_to_string` method to the `FileProcessor` trait and implement.

```diff
 use std::path::Path;
 use x509_parser::pem::pem_to_der;
 
 trait FileProcessor {
     fn is_file(&self, path: &str) -> bool;
+    fn read_to_string(&self, path: &str) -> Result<String, Box<dyn std::error::Error>>;
 }
 
 struct CertProcessor;
 
 impl FileProcessor for CertProcessor {
     fn is_file(&self, path: &str) -> bool {
         Path::new(path).is_file()
     }
+    fn read_to_string(&self, path: &str) -> Result<String, Box<dyn std::error::Error>> {
+        Ok(std::fs::read_to_string(path)?)
+    }
 }
 
 fn execute(
-    validator: impl FileProcessor,
+    processor: impl FileProcessor,
     args: Vec<String>,
 ) -> Result<(), Box<dyn std::error::Error>> {
     if args.len() != 1 {
         let error = format!(
             "{}{}",
             "Error: did not receive a single argument, ",
             "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
         );
         return Err(error.into());
     }
     let path = &args[0];
-    if !validator.is_file(path) {
+    if !processor.is_file(path) {
         return Err(
             "Error: path given is not a regular file, please update to point to a certificate."
                 .into(),
         );
     }
-    let cert = std::fs::read_to_string(path).unwrap();
+    let cert = processor.read_to_string(path)?;
     let _ = pem_to_der(cert.as_bytes())?;
     Ok(())
 }
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let args = std::env::args().skip(1).collect();
     let validator = CertProcessor;
     execute(validator, args)
 }
 
 #[cfg(test)]
 mod test {
 
     use crate::{execute, FileProcessor};
 
     struct FakeProcessor {
         is_file: bool,
+        file_str: String,
     }
 
     impl FileProcessor for FakeProcessor {
         fn is_file(&self, _: &str) -> bool {
             self.is_file
         }
+        fn read_to_string(&self, _: &str) -> Result<String, Box<dyn std::error::Error>> {
+            Ok(self.file_str.clone())
+        }
     }
 
     #[test]
     fn should_error_if_not_given_a_single_argument() {
         // arrange
         let args = Vec::new();
-        let validator = FakeProcessor { is_file: false };
+        let validator = FakeProcessor {
+            is_file: false,
+            file_str: "".to_owned(),
+        };
 
         // act
         let result = execute(validator, args);
 
         // assert
         assert!(result.is_err());
         assert_eq!(
             format!("{}", result.err().unwrap()),
             format!(
                 "{}{}",
                 "Error: did not receive a single argument, ",
                 "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
             )
         );
     }
 
     #[test]
     fn should_error_if_argument_is_not_a_regular_file() {
         // arrange
         let args = vec!["not-a-regular-file".to_owned()];
-        let validator = FakeProcessor { is_file: false };
+        let validator = FakeProcessor {
+            is_file: false,
+            file_str: "".to_owned(),
+        };
 
         // act
         let result = execute(validator, args);
 
         // assert
         assert!(result.is_err());
         assert_eq!(
             format!("{}", result.err().unwrap()),
             "Error: path given is not a regular file, please update to point to a certificate."
         );
     }
 
     #[test]
     fn should_error_if_given_argument_is_not_a_pem_encoded_certificate() {
         let args = vec!["Cargo.toml".to_owned()];
-        let validator = FakeProcessor { is_file: true };
+        let validator = FakeProcessor {
+            is_file: true,
+            file_str: "".to_owned(),
+        };
         let result = execute(validator, args);
         assert!(result.is_err())
     }
 
     #[test]
     fn should_succeed() {
-        let args = vec!["resources/google.com.crt".to_owned()];
-        let validator = FakeProcessor { is_file: true };
+        let cert = include_str!("../resources/google.com.crt");
+        let args = vec!["doesnt-really-matter".to_owned()];
+        let validator = FakeProcessor {
+            is_file: true,
+            file_str: cert.to_owned(),
+        };
         let result = execute(validator, args);
         assert!(result.is_ok());
     }
```
> **:memo: Note**
> 
> In the `should_succeed` test we use the [include_str](https://doc.rust-lang.org/std/macro.include_str.html) macro to read the real cert at compile time. This is cleaner than pasting the cert directly in the test. 
 

> :link: [wayofthepie/cert-decoder@436d85f](https://github.com/wayofthepie/cert-decoder/tree/436d85f6a6da208181738d186029462c66c53511)

We can improve the tests by deriving[^deriving] [Default](https://doc.rust-lang.org/std/default/trait.Default.html) for our `FakeProcessor`. This will give us a basic implementation of `FakeProcessor`, defaulting all the fields to the value of the `Default` implementation for their type. For example, the default for `bool` is `false` and for `String` is the empty string.

```diff
 #[cfg(test)]
 mod test {
 
     use crate::{execute, FileProcessor};
 
     ...
-
+    #[derive(Default)]
     struct FakeProcessor {
         is_file: bool,
         file_str: String,
     }
 
     #[test]
     fn should_error_if_not_given_a_single_argument() {
-        // arrange
         let args = Vec::new();
-        let validator = FakeProcessor {
-            is_file: false,
-            file_str: "".to_owned(),
-        };
-
-        // act
+        let validator = FakeProcessor::default();
         let result = execute(validator, args);
-
-        // assert
         assert!(result.is_err());
         assert_eq!(
             format!("{}", result.err().unwrap()),
             format!(
                 "{}{}",
                 "Error: did not receive a single argument, ",
                 "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
             )
         );
     }
 
     #[test]
     fn should_error_if_argument_is_not_a_regular_file() {
-        // arrange
         let args = vec!["not-a-regular-file".to_owned()];
-        let validator = FakeProcessor {
-            is_file: false,
-            file_str: "".to_owned(),
-        };
-
-        // act
+        let validator = FakeProcessor::default();
         let result = execute(validator, args);
-
-        // assert
         assert!(result.is_err());
         assert_eq!(
             format!("{}", result.err().unwrap()),
             "Error: path given is not a regular file, please update to point to a certificate."
         );
     }
 
     #[test]
     fn should_error_if_given_argument_is_not_a_pem_encoded_certificate() {
         let args = vec!["Cargo.toml".to_owned()];
         let validator = FakeProcessor {
             is_file: true,
-            file_str: "".to_owned(),
+            ..FakeProcessor::default()
         };
         let result = execute(validator, args);
         assert!(result.is_err())
     }
     ... 
 }
```
> **:memo: Note**
> In a test above we used [struct update syntax](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax), `..FakeProcessor::default()`. This will "fill in" any fields we do not explicitly set. It will allow us to add more fields to `FileProcessor` if needed and not have to update all tests. 

After each change, you should run the tests! If you run them now they should still be green.

```bash
✦ ➜ cargo -q test

running 4 tests
....
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

> :link: [wayofthepie/cert-decoder@e984bd7f](https://github.com/wayofthepie/cert-decoder/tree/e984bd7ffea42e30cd35bb6f65b7a7808c9be777)

## Parse the der encoded cert
Let's parse the DER bytes into an `X509Certificate`. In the [x509 parser API](#x509parser-api) section we saw an example of this using the [parse_x509_der](https://docs.rs/x509-parser/0.7.0/x509_parser/fn.parse_x509_der.html) function. It can fail, so first, a test.

```rust
#[test]
fn should_error_if_argument_is_not_a_valid_certificate() {
    let cert = include_str!("../resources/bad.crt");
    let args = vec!["doesnt-really-matter".to_owned()];
    let processor = FakeProcessor {
        is_file: true,
        file_str: cert.to_owned(),
    };
    let result = execute(processor, args);
    assert!(result.is_err());
}
```
I have added a file called `bad.crt` to the resources folder. This just contains a base64 encoded string, which is not a valid certificate. So it will succeed in the `pem_to_der` call but calling `parse_x509_der` should return an error. First, let's see this test fail.

```bash
✦ ➜ cargo -q test

running 5 tests
...F.
failures:

---- test::should_error_if_argument_is_not_a_valid_certificate stdout ----
thread 'test::should_error_if_argument_is_not_a_valid_certificate' panicked at 'assertion failed: result.is_err()', src/main.rs:118:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test::should_error_if_argument_is_not_a_valid_certificate

test result: FAILED. 4 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--bin cert-decode'
```
Great! Now, let's parse the DER we get.

```rust 
fn execute(
    processor: impl FileProcessor,
    args: Vec<String>,
) -> Result<(), Box<dyn std::error::Error>> {
    // stripped out irrelevant code 
    ...
    
    let cert = processor.read_to_string(path)?;
    let (_, pem) = pem_to_der(cert.as_bytes())?;
    let _ = parse_x509_der(&pem.contents)?;
    Ok(())
}
```
And re-run the tests.

```bash
✦ ➜ cargo -q test

running 5 tests
.....
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

Great! We didn't need to update the `should_succeed` test, meaning it is reading our real certificate correctly. There are few things we can improve here, but first, let's mark off the first item in our test list.

---
**Read and print certificate**  
- :heavy_check_mark: Validate file is a certificate
- ☐ Print the certificate 

---

> :link: [wayofthepie/cert-decoder@11ff773](https://github.com/wayofthepie/cert-decoder/tree/11ff773576691584fc1400176b9bff7778fea2b5)

## Print the certificate
It turns out we will have to do a bit more processing to get a human-readable output format, so I'm going to cheat here! On success, `parse_x509_der` returns a tuple with remaining bytes and an `X509Certificate`. The `X509Certificate` type implements [Debug](https://doc.rust-lang.org/std/fmt/trait.Debug.html) so we can print its debug format.

```diff

 fn execute(
     processor: impl FileProcessor,
     args: Vec<String>,
 ) -> Result<(), Box<dyn std::error::Error>> {
     ... 
     let cert = processor.read_to_string(path)?;
     let (_, pem) = pem_to_der(cert.as_bytes())?;
-    let _ = parse_x509_der(&pem.contents)?;
+    let (_, cert) = parse_x509_der(&pem.contents)?;
+    let output = format!("{:#?}", cert.tbs_certificate);
+    println!("{}", output);
     Ok(())
 }

 ...

```

We use the debug format specifier `{:?}` in the `format` macro. We also add a `#` to pretty print it, `{:#?}`. The only thing we print here is the `tbs_certificate` field as that contains all the details we will need. To test this, let's run the actual cli.

> **:memo: Note**
> From the above change you might be thinking "Why create a pre-formatted string and pass that to `println!`? Couldn't you just use the debug format specifier directly in `println!`?". You can do this, try it and do `cargo -q run -- resources/google.com.crt`. Now do it again with a pipe - `cargo -q run -- resources/google.com.crt | head -n20` - it will fail. I may do a short post on why this happens. 

```bash
✦ ➜ cargo -q run -- resources/google.com.crt | head -n20
TbsCertificate {
    version: 2,
    serial: BigUint {
        data: [
            4412903,
            134217728,
            247394332,
            1459281101,
        ],
    },
    signature: AlgorithmIdentifier {
        algorithm: OID(1.2.840.113549.1.1.11),
        parameters: BerObject {
            class: 0,
            structured: 0,
            tag: EndOfContent,
            content: ContextSpecific(
                EndOfContent,
                Some(
                    BerObject {
```
Pretty unreadable! But it's a start, we're making some headway. In the next post, we'll clean this up.

---
**Read and print certificate**  
- :heavy_check_mark: Validate file is a certificate
- :heavy_check_mark: Print the certificate 

---


> :link: [wayofthepie/cert-decoder@11058e3](https://github.com/wayofthepie/cert-decoder/tree/11058e3760ee31a0f47ba57e50af016b8ca9760b)

# Tiny refactor
I realized I misnamed a few things. I missed them when refactoring. In the tests, most of the `FakeProcessor` variables are still called `validator`. Similarly in `main`. Let's update those.

```diff
  ...

 fn main() -> Result<(), Box<dyn std::error::Error>> {
     let args = std::env::args().skip(1).collect();
-    let validator = CertProcessor;
-    execute(validator, args)
+    let processor = CertProcessor;
+    execute(processor, args)
 }
 
 #[cfg(test)]
 mod test {
 
     ...
     
 
     #[test]
     fn should_error_if_not_given_a_single_argument() {
         let args = Vec::new();
-        let validator = FakeProcessor::default();
-        let result = execute(validator, args);
+        let processor = FakeProcessor::default();
+        let result = execute(processor, args);
         assert!(result.is_err());
         assert_eq!(
             format!("{}", result.err().unwrap()),
             format!(
                 "{}{}",
                 "Error: did not receive a single argument, ",
                 "please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
             )
         );
     }
 
     #[test]
     fn should_error_if_argument_is_not_a_regular_file() {
         let args = vec!["not-a-regular-file".to_owned()];
-        let validator = FakeProcessor::default();
-        let result = execute(validator, args);
+        let processor = FakeProcessor::default();
+        let result = execute(processor, args);
         assert!(result.is_err());
         assert_eq!(
             format!("{}", result.err().unwrap()),
             "Error: path given is not a regular file, please update to point to a certificate."
         );
     }
 
     #[test]
     fn should_error_if_given_argument_is_not_a_pem_encoded_certificate() {
         let args = vec!["Cargo.toml".to_owned()];
-        let validator = FakeProcessor {
+        let processor = FakeProcessor {
             is_file: true,
             ..FakeProcessor::default()
         };
-        let result = execute(validator, args);
+        let result = execute(processor, args);
         assert!(result.is_err())
     }
 
     ...

     #[test]
     fn should_succeed() {
         let cert = include_str!("../resources/google.com.crt");
         let args = vec!["doesnt-really-matter".to_owned()];
-        let validator = FakeProcessor {
+        let processor = FakeProcessor {
             is_file: true,
             file_str: cert.to_owned(),
         };
-        let result = execute(validator, args);
+        let result = execute(processor, args);
         println!("{:#?}", result);
         assert!(result.is_ok());
     }
 }
```

> :link: [wayofthepie/cert-decoder@371ea84](https://github.com/wayofthepie/cert-decoder/tree/371ea84cbd017c1375b71399a7539d45b6d4ea5f)

# Conclusion
There were a few things I glossed over that appeared in this post. I will take note of them and make sure they appear in one of the next posts. For example lifetimes, a better explanation of `Box`, and that `println` issue I mentioned in a note in the last section.

There is also a small display issue when an error occurs. For example, if we pass no argument:

```bash
✦ ➜ cargo -q run --
Error: "Error: did not receive a single argument, please invoke cert-decoder as follows: ./cert-decoder /path/to/cert."
```
It repeats the word "Error" and also wraps our error string in quotes. We will fix this too in the next post!

[^parser-combinator]: [noms readme](https://github.com/Geal/nom/blob/master/README.md) has a short overview of [parser combinators](https://github.com/Geal/nom#parser-combinators).
[^type-alias]: See [Advanced Types](https://doc.rust-lang.org/book/ch19-04-advanced-types.html?highlight=Type,alias#advanced-types) in the [Rust Book](https://doc.rust-lang.org/book/title-page.html) for more on type aliases.
[^generics-lifetimes]: See [Generic Types, Traits, and Lifetimes](https://doc.rust-lang.org/book/ch10-00-generics.html?highlight=generics#generic-types-traits-and-lifetimes) in the [Rust book](https://doc.rust-lang.org/book/).
[^error-handling]: Error handling in Rust is a big topic, to get started see the [Error Handling](https://doc.rust-lang.org/stable/rust-by-example/error.html) chapter in the [Rust book](https://doc.rust-lang.org/book/). For more on boxed errors see [Boxing errors](https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html).d
[^trait-objects]: See [Using Trait Objects That Allow for Values of Different Types](https://doc.rust-lang.org/book/ch17-02-trait-objects.html) in the [Rust book](https://doc.rust-lang.org/book/).
[^question-mark]: For more on how the `?` works see [The ? operator for easier error handling](https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html).
[^deriving]: For more on deriving see [Derive](https://doc.rust-lang.org/stable/rust-by-example/trait/derive.html)