JC Tips and Tricks – Part 1

Subnet Calculator | X.509 Certificates | Date and Timestamp Conversions

It’s hard to believe it’s been about five years since I started building jc! I often get notes from users around the world about how jc has helped them out. It has been described as a Swiss Army Knife for the command line and scripting, so I thought it would be fun and helpful to describe some of my favorite use cases for jc. To keep these posts digestible, I’ll go over three use cases per post, so there will be a few more of these in the next few weeks.

In this post I’ll go over some interactive use-cases at the command line:

  • Subnet Calculator
  • Exploring X.509 Certificates and Certificate Signing Requests
  • Converting and pulling data from Date and Timestamp strings

Your New Favorite Subnet Calculator

I started my career as a network engineer, so calculating subnets is in my DNA. My binary math is not as good as it used to be, so sometimes it’s good to have a handy tool to break down and IP address and give you the pertinent information you’re after.

There are several subnet calculators available on the web and as CLI utilities. I found that many times they don’t show all of the details because they are optimized for interactive human consumption instead of being optimized for use in scripts. Since jc outputs in JSON, you can get the best of both worlds along with easy filtering of output with tools like jq.

Let’s take a look at a simple example:

% echo 192.168.1.10/25 | jc --ip-address --pretty 
{
  "version": 4,
  "max_prefix_length": 32,
  "ip": "192.168.1.10",
  "ip_compressed": "192.168.1.10",
  "ip_exploded": "192.168.1.10",
  "ip_split": [
    "192",
    "168",
    "1",
    "10"
  ],
  "scope_id": null,
  "ipv4_mapped": null,
  "six_to_four": null,
  "teredo_client": null,
  "teredo_server": null,
  "dns_ptr": "10.1.168.192.in-addr.arpa",
  "network": "192.168.1.0",
  "broadcast": "192.168.1.127",
  "hostmask": "0.0.0.127",
  "netmask": "255.255.255.128",
  "cidr_netmask": 25,
  "hosts": 126,
  "first_host": "192.168.1.1",
  "last_host": "192.168.1.126",
  "is_multicast": false,
  "is_private": true,
  "is_global": false,
  "is_link_local": false,
  "is_loopback": false,
  "is_reserved": false,
  "is_unspecified": false,
  "int": {
    "ip": 3232235786,
    "network": 3232235776,
    "broadcast": 3232235903,
    "first_host": 3232235777,
    "last_host": 3232235902
  },
  "hex": {
    "ip": "c0:a8:01:0a",
    "network": "c0:a8:01:00",
    "broadcast": "c0:a8:01:7f",
    "hostmask": "00:00:00:7f",
    "netmask": "ff:ff:ff:80",
    "first_host": "c0:a8:01:01",
    "last_host": "c0:a8:01:7e"
  },
  "bin": {
    "ip": "11000000101010000000000100001010",
    "network": "11000000101010000000000100000000",
    "broadcast": "11000000101010000000000101111111",
    "hostmask": "00000000000000000000000001111111",
    "netmask": "11111111111111111111111110000000",
    "first_host": "11000000101010000000000100000001",
    "last_host": "11000000101010000000000101111110"
  }
}

Notice you simply pipe the IP address to jc. This is important because it allows you to easily grab the details of an IP address in a Bash pipeline. I used the --pretty (or -p) option to pretty-print the output. By the way, I could have also used --yaml-out (or -y) to see a slightly simpler form:

% echo 192.168.1.10/25 | jc --ip-address --yaml-out         
---
version: 4
max_prefix_length: 32
ip: 192.168.1.10
ip_compressed: 192.168.1.10
ip_exploded: 192.168.1.10
<snip>

Note: You can specify the subnet as a CIDR prefix or in legacy dot notation.

If you’re a network geek like me you’ll notice a ton of useful information about the IP and subnet. If you are writing a script that needs to know whether the IP/subnet is valid IPv4 or IPv6, you can query the version field. You can grab each octet of the IP with the ip_split field. Of course you get the network, number of hosts, first and last host, and the broadcast address. But you also get a ton of other useful information!

Have you ever needed the integer representation of an IP address? jc gives you that, plus hex and binary for all of the interesting values.

Do you need to know whether an IP address is private, public, or multicast? Or what the 6-to-4 mapping IP is in an IPv6 address? It’s all there!

% echo 2002:c000:204::/48 | jc --ip-address | jq -r .six_to_four
192.0.2.4

Another cool trick is that the --ip-address parer is slurpable. That means you can use the --slurp (or -s) option in jc and send multiple line-delimited IP addresses to have all of the output slurped into a JSON array:

% cat ipaddresses.txt 
1.1.1.1
10.10.10.10
192.168.1.53
127.0.0.1
::1
% cat ipaddresses.txt | jc --slurp --pretty --ip-address
[
  {<1.1.1.1 information>},
  {<10.10.10.10 information>},
  {<192.168.1.53 information>},
  {<127.0.0.1 information>},
  {<::1 information>}
]

There are a lot of slurpable parsers in jc. You can use jc -hhh to see which parsers can use the --slurp option.

That was fun, now on to the wonderful world of certificates.

Exploring X.509 Certificates

I wont spend too much time on this section since I wrote a blog post on this subject before, but this is the most highly searched topic that hits this site, so I figured it might be helpful for more people to know about it.

When I started developing jc, I was focusing on converting command output to JSON to make scripting easier. I soon realized that there was tremendous value in converting common strings and filetypes to JSON as well. X.509 certificates can be a pain to work with because their form factor is designed for signing and authenticating transactions. If you need to grab a certain value from the certificate, then you need to understand its format and parse the output of a program like openssl.

The nice thing is that jc lets you easily grab any field from a certificate or CSR – even binary DER formatted certificates and CSRs, natively.

% cat x509-ca-cert.der | jc --x509-cert --pretty
[
  {
    "tbs_certificate": {
      "version": "v3",
      "serial_number": "60:01:97:b7:46:a7:ea:b4:b4:9a:d6:4b:2f:f7:90:fb",
      "signature": {
        "algorithm": "sha256_rsa",
        "parameters": null
      },
      "issuer": {
        "country_name": "US",
        "organization_name": "thawte, Inc.",
        "organizational_unit_name": [
          "Certification Services Division",
          "(c) 2008 thawte, Inc. - For authorized use only"
        ],
        "common_name": "thawte Primary Root CA - G3"
      },
      "validity": {
        "not_before": 1207094400,
        "not_after": 2143324799,
        "not_before_iso": "2008-04-02T00:00:00+00:00",
        "not_after_iso": "2037-12-01T23:59:59+00:00"
      },
      "subject": {
        "country_name": "US",
        "organization_name": "thawte, Inc.",
        "organizational_unit_name": [
          "Certification Services Division",
          "(c) 2008 thawte, Inc. - For authorized use only"
        ],
        "common_name": "thawte Primary Root CA - G3"
      },
      "subject_public_key_info": {
        "algorithm": {
          "algorithm": "rsa",
          "parameters": null
        },
        "public_key": {
          "modulus": "b2:bf:27:2c:fb:db:d8:5b:dd:78:7b:1b:9e:77:66:81:cb:3e:bc:7c:ae:f3:a6:27:9a:34:a3:68:31:71:38:33:62:e4:f3:71:66:79:b1:a9:65:a3:a5:8b:d5:8f:60:2d:3f:42:cc:aa:6b:32:c0:23:cb:2c:41:dd:e4:df:fc:61:9c:e2:73:b2:22:95:11:43:18:5f:c4:b6:1f:57:6c:0a:05:58:22:c8:36:4c:3a:7c:a5:d1:cf:86:af:88:a7:44:02:13:74:71:73:0a:42:59:02:f8:1b:14:6b:42:df:6f:5f:ba:6b:82:a2:9d:5b:e7:4a:bd:1e:01:72:db:4b:74:e8:3b:7f:7f:7d:1f:04:b4:26:9b:e0:b4:5a:ac:47:3d:55:b8:d7:b0:26:52:28:01:31:40:66:d8:d9:24:bd:f6:2a:d8:ec:21:49:5c:9b:f6:7a:e9:7f:55:35:7e:96:6b:8d:93:93:27:cb:92:bb:ea:ac:40:c0:9f:c2:f8:80:cf:5d:f4:5a:dc:ce:74:86:a6:3e:6c:0b:53:ca:bd:92:ce:19:06:72:e6:0c:5c:38:69:c7:04:d6:bc:6c:ce:5b:f6:f7:68:9c:dc:25:15:48:88:a1:e9:a9:f8:98:9c:e0:f3:d5:31:28:61:11:6c:67:96:8d:39:99:cb:c2:45:24:39",
          "public_exponent": 65537
        }
      },
      "issuer_unique_id": null,
      "subject_unique_id": null,
      "extensions": [
        {
          "extn_id": "basic_constraints",
          "critical": true,
          "extn_value": {
            "ca": true,
            "path_len_constraint": null
          }
        },
        {
          "extn_id": "key_usage",
          "critical": true,
          "extn_value": [
            "crl_sign",
            "key_cert_sign"
          ]
        },
        {
          "extn_id": "key_identifier",
          "critical": false,
          "extn_value": "ad:6c:aa:94:60:9c:ed:e4:ff:fa:3e:0a:74:2b:63:03:f7:b6:59:bf"
        }
      ],
      "serial_number_str": "127614157056681299805556476275995414779"
    },
    "signature_algorithm": {
      "algorithm": "sha256_rsa",
      "parameters": null
    },
    "signature_value": "1a:40:d8:95:65:ac:09:92:89:c6:39:f4:10:e5:a9:0e:66:53:5d:78:de:fa:24:91:bb:e7:44:51:df:c6:16:34:0a:ef:6a:44:51:ea:2b:07:8a:03:7a:c3:eb:3f:0a:2c:52:16:a0:2b:43:b9:25:90:3f:70:a9:33:25:6d:45:1a:28:3b:27:cf:aa:c3:29:42:1b:df:3b:4c:c0:33:34:5b:41:88:bf:6b:2b:65:af:28:ef:b2:f5:c3:aa:66:ce:7b:56:ee:b7:c8:cb:67:c1:c9:9c:1a:18:b8:c4:c3:49:03:f1:60:0e:50:cd:46:c5:f3:77:79:f7:b6:15:e0:38:db:c7:2f:28:a0:0c:3f:77:26:74:d9:25:12:da:31:da:1a:1e:dc:29:41:91:22:3c:69:a7:bb:02:f2:b6:5c:27:03:89:f4:06:ea:9b:e4:72:82:e3:a1:09:c1:e9:00:19:d3:3e:d4:70:6b:ba:71:a6:aa:58:ae:f4:bb:e9:6c:b6:ef:87:cc:9b:bb:ff:39:e6:56:61:d3:0a:a7:c4:5c:4c:60:7b:05:77:26:7a:bf:d8:07:52:2c:62:f7:70:63:d9:39:bc:6f:1c:c2:79:dc:76:29:af:ce:c5:2c:64:04:5e:88:36:6e:31:d4:40:1a:62:34:36:3f:35:01:ae:ac:63:a0"
  }
]

Notice how easy that makes it to grab any value you want? For example, to grab the Common Name field:

% cat x509-ca-cert.der | jc --x509-cert | jq -r '.[0].tbs_certificate.subject.common_name'
thawte Primary Root CA - G3

Also notice that the validity dates have already been converted to Unix epoch timestamps and ISO datetime format for easier use in scripts and automation:

% cat x509-ca-cert.der | jc --x509-cert | jq -r '.[0].tbs_certificate.validity'           
{
  "not_before": 1207094400,
  "not_after": 2143324799,
  "not_before_iso": "2008-04-02T00:00:00+00:00",
  "not_after_iso": "2037-12-01T23:59:59+00:00"
}

Be sure to check out my earlier blog post on this subject for more examples.

Converting Datetimes and Timestamps

There is no shortage of date and time formats to choose from. There’s Unix date command output, the ISO 8601 standard, Unix epoch timestamps, and more. When writing a script you might want to convert from one format to another, or maybe you just want to grab a certain part of the datetime… perhaps just the day?

jc let’s you do all of the above! Here’s an example of what information jc gives you for the Unix date command output:

% date | jc --date --pretty
{
  "year": 2024,
  "month": "Feb",
  "month_num": 2,
  "day": 6,
  "weekday": "Tue",
  "weekday_num": 2,
  "hour": 6,
  "hour_24": 18,
  "minute": 0,
  "second": 13,
  "period": "PM",
  "timezone": "PST",
  "utc_offset": null,
  "day_of_year": 37,
  "week_of_year": 6,
  "iso": "2024-02-06T18:00:13",
  "epoch": 1707271213,
  "epoch_utc": null,
  "timezone_aware": false
}

Notice, you not only get Unix epoch timestamp conversions, but you also get an ISO conversion. In addition, it’s easy to grab any part of the date output. Maybe you want to convert from 12 hour to 24 hour? To do that you can grab the hour_24 field. jc also calculates the day of the year for you with the day_of_year field. You can also get the days and months in text and number formats. There’s a lot more going on here than simply parsing the items of the date command output.

Let’s do the same thing with an ISO datetime string:

% echo 2024-02-06T18:00:13-08:00 | jc --datetime-iso --pretty
{
  "year": 2024,
  "month": "Feb",
  "month_num": 2,
  "day": 6,
  "weekday": "Tue",
  "weekday_num": 2,
  "hour": 6,
  "hour_24": 18,
  "minute": 0,
  "second": 13,
  "microsecond": 0,
  "period": "PM",
  "utc_offset": "-0800",
  "day_of_year": 37,
  "week_of_year": 6,
  "iso": "2024-02-06T18:00:13-08:00",
  "timestamp": 1707271213
}

Very similar information is available. And we can do the same thing with a Unix epoch timestamp:

% echo 1707271213 | jc --timestamp --pretty
{
  "naive": {
    "year": 2024,
    "month": "Feb",
    "month_num": 2,
    "day": 6,
    "weekday": "Tue",
    "weekday_num": 2,
    "hour": 6,
    "hour_24": 18,
    "minute": 0,
    "second": 13,
    "period": "PM",
    "day_of_year": 37,
    "week_of_year": 6,
    "iso": "2024-02-06T18:00:13"
  },
  "utc": {
    "year": 2024,
    "month": "Feb",
    "month_num": 2,
    "day": 7,
    "weekday": "Wed",
    "weekday_num": 3,
    "hour": 2,
    "hour_24": 2,
    "minute": 0,
    "second": 13,
    "period": "AM",
    "utc_offset": "+0000",
    "day_of_year": 38,
    "week_of_year": 6,
    "iso": "2024-02-07T02:00:13+00:00"
  }
}

Whoa, what is that? Naive and UTC? In most cases you will want to use the utc time since almost all Unix timestamps are generated and used that way. The naive time is based off of the local timezone of the machine jc is running on.

In any case, this allows you to grab any information from the timestamp. Let’s say you want to grab the week of the year:

% echo 1707272432 | jc --timestamp | jq .utc.week_of_year
6

By the way, these date parsers are also slurpable, just like the --ip-address parser.

So there you have it – the first three jc tips and tricks. What are some of your favorite use cases? I hope you enjoyed these and I’ll post some more soon!

Published by kellyjonbrazil

I'm a cybersecurity and cloud computing nerd.

Leave a Reply

Discover more from Brazil's Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading