Convert X.509 Certificates to JSON with JC

There are some cool hacks out there that will help you extract X.509 certificate metadata to JSON values. Since jc converts so many other things to JSON, I figured it would make sense to add this functionality. I wanted to make sure jc could handle both binary and text-encoded certificates of most any type, well-known and user-defined extensions, and also ensure the output was convenient for use in scripts.

At first, I considered parsing -text output from openssl. It would not have been too hard to do – except for finding a way to reliably parse unknown certificate extensions. Ultimately I wanted to not only support openssl output, but even native certificate file formats so you could pipe the certificate file directly to jc like: cat certificate.crt | jc --x509-cert.

If I had gone the original route I would have needed two parsers: one openssl parser and another X.509 certificate parser – maybe even multiple parsers for different certificate formats.

I started building an X.509 certificate file parser that supports DER and PEM-encoded certificates first. Serendipitously, I found that this method provides all of the desired functionality in a single parser! This method suports:

  • Most any binary certificate format (DER, PKCS #7, PKCS #12, etc.)
  • PEM-encoded certificates
  • openssl command output (and any other command that can output DER and PEM)
    • Allows conversion of password-protected certificate files to JSON
    • Allows conversion of most any certificate format to JSON
  • Well-known and user-defined X.509 certificate extensions
  • Certificate files with multiple certificates bundled
  • Convenience fields (e.g. dates in timestamp format as well as ISO format)

Converting DER Certificate Files to JSON

The most basic (but not necessarily the most popular) X.509 certificate file is simply a binary DER format certificate. Certificate file extensions are arbitrary, so there is no guarantee you have simple DER-encoded certificate by looking at the extension. But typical file extensions for DER-encoded certificates can be .der, .cer, .crt, etc.

jc can natively convert DER-encoded certificate files to JSON:

$ cat certificate.crt | jc --x509-cert

Converting PEM Certificate Files to JSON

PEM encoded certificate files are pretty much just base64-encoded DER certificates and will many times have a .pem file extension. But, again, certificate file extensions are arbitrary, so a valid PEM file could also have a .crt, .cer, or any other extension.

PEM files can also contain more than one certificate. For instance, there might be a certificate chain with the web server certificate and one or more intermediate certificates encoded in the file. Also, a PEM file can include other objects like private keys. Don’t worry – jc can handle multiple certificates and will ignore anything but certificates in the JSON output.

jc can natively convert PEM-encoded certificates to JSON:

$ cat certificate.pem | jc --x509-cert

Converting PKCS #7 Certificate Files to JSON

PKCS #7 certificates will typically have a .p7b or .p7c file extension and can be either binary DER-encoded or text PEM-encoded.

jc will not natively convert PKCS #7 certificate files to JSON, but don’t worry! You can easily convert the PKCS #7 file to vanilla X.509 DER or PEM with openssl so jc can convert it to JSON:

$ openssl pkcs7 \
          -in certificate.p7b \
          -inform der \
          -print_certs | jc --x509-cert

Note that the -inform argument is not needed if the PKCS #7 file is PEM encoded.

Converting PKCS #12 Certificate Files to JSON

PKCS #12 files are a password-protected binary format that can contain certificates, private keys, and other objects. You will typically see a .pfx or .p12 extension on these files.

jc will not natively convert binary PKCS #12 certificate files to JSON, but don’t worry! You can easily convert the PKCS #12 file to vanilla X.509 DER or PEM with openssl so jc can convert it to JSON:

$ openssl pkcs12 \
          -info \
          -in certificate.pfx \
          -passin pass:abc123 \
          -passout pass: | jc --x509-cert

Note that you need to specify the certificate file password in the -passin parameter. You can set any password to the -passout parameter so you won’t be prompted for one when the command is run. In this example we set it to blank.

Using in a Script

Let’s put all of the pieces together and show how you can use JSON output in a script.

No matter the certificate type, the JSON output will be consistent. The schema can be found in the jc documentation for the X.509 certificate parser. Here is an example of a Certificate Authority certificate converted from a PKCS #12 file:

[
  {
    "tbs_certificate": {
      "version": "v3",
      "serial_number": "e1:3f:bc:97:7c:10:1d:b8",
      "signature": {
        "algorithm": "sha1_rsa",
        "parameters": null
      },
      "issuer": {
        "country_name": "FR",
        "state_or_province_name": "Alsace",
        "locality_name": "Strasbourg",
        "organization_name": "www.freelan.org",
        "organizational_unit_name": "freelan",
        "common_name": "Freelan Sample Certificate Authority",
        "email_address": "contact@freelan.org"
      },
      "validity": {
        "not_before": 1335521864,
        "not_after": 1338113864,
        "not_before_iso": "2012-04-27T10:17:44+00:00",
        "not_after_iso": "2012-05-27T10:17:44+00:00"
      },
      "subject": {
        "country_name": "FR",
        "state_or_province_name": "Alsace",
        "locality_name": "Strasbourg",
        "organization_name": "www.freelan.org",
        "organizational_unit_name": "freelan",
        "common_name": "Freelan Sample Certificate Authority",
        "email_address": "contact@freelan.org"
      },
      "subject_public_key_info": {
        "algorithm": {
          "algorithm": "rsa",
          "parameters": null
        },
        "public_key": {
          "modulus": "e0:e9:fb:ca:10:70:af:8c:4e:e5:8f:65:5c:49:65:1e:f9:a5:a2:b8:cd:c5:27:82:ea:58:5d:64:86:58:55:cf:4d:5e:ef:b2:c1:64:ea:f2:27:78:f0:2b:4c:bf:93:...",
          "public_exponent": 65537
        }
      },
      "issuer_unique_id": null,
      "subject_unique_id": null,
      "extensions": [
        {
          "extn_id": "key_identifier",
          "critical": false,
          "extn_value": "23:6c:2d:3d:3e:29:5d:78:b8:6c:3e:aa:e2:bb:2e:1e:6c:87:f2:53"
        },
        {
          "extn_id": "authority_key_identifier",
          "critical": false,
          "extn_value": {
            "key_identifier": "23:6c:2d:3d:3e:29:5d:78:b8:6c:3e:aa:e2:bb:2e:1e:6c:87:f2:53",
            "authority_cert_issuer": null,
            "authority_cert_serial_number": null
          }
        },
        {
          "extn_id": "basic_constraints",
          "critical": false,
          "extn_value": {
            "ca": true,
            "path_len_constraint": null
          }
        }
      ]
    },
    "signature_algorithm": {
      "algorithm": "sha1_rsa",
      "parameters": null
    },
    "signature_value": "b0:44:9a:49:0a:0a:7b:4b:e9:3d:05:3e:97:de:40:5e:7e:89:c4:10:e6:2d:c9:65:c1:3e:9b:b2:1b:74:25:9b:5a:dd:85:ce:ba:0c:21:85:a2:b0:e6:4f:18:cc:98:..."
  }
]

Note: jc does not verify the integrity of the certificate, which requires calculating the hash of the certificate body and comparing it to the the hash in the certificate’s signature after it (the hash) is decrypted with the issuer certificate’s public key.

Notice the first (and only) certificate in this JSON array has a tbs_certificate.validity object that contains not_before and not_after values in both epoch timestamp and ISO formats. This should make it easy for us to check whether the certificate is valid in a Bash script using a JSON parser like jq:

#!/bin/bash

# grab the validity information from the first certificate in the pkcs12 file
cert_json=$(
    openssl pkcs12 \
        -info \
        -in certificate.pfx \
        -passin pass:abc123 \
        -passout pass: | jc --x509-cert
)

not_before=$(
    echo "$cert_json" | jq .[0].tbs_certificate.validity.not_before
)

not_after=$(
    echo "$cert_json" | jq .[0].tbs_certificate.validity.not_after
)

# compare the timestamps to the current time
current_time=$(date '+%s')

if [[ "$not_before" -lt "$current_time" ]] && [[ "$not_after" -gt "$current_time" ]]; then
    echo "Certificate is valid"
else
    echo "Certificate is invalid"
fi

And here is the output for an expired certificate. (note the STDERR and STDOUT lines have been distinguished):

$ ./checkcert.sh
MAC Iteration 2048                                                    # STDERR
MAC verified OK                                                       # STDERR
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048      # STDERR
Certificate bag                                                       # STDERR
PKCS7 Data                                                            # STDERR
Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 2048     # STDERR
Certificate is invalid                                                # STDOUT

There are a lot more things to check than just the not_before and not_after fields for a true certificate validation, so this should be considered a toy example to get you started. I hope this new jc X.509 certificate parser helps you in your automation scripts!

Published by kellyjonbrazil

I'm a cybersecurity and cloud computing nerd.

Leave a Reply

%d bloggers like this: