Skip to content

Commit 0a659c5

Browse files
committed
certinfo MVP
1 parent 97f3ff6 commit 0a659c5

6 files changed

Lines changed: 161 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*.class
55
crash.log
66
_data/build.json
7+
dist
78
.DS_Store
89
.env
910
Gemfile.lock

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
- [ ] `anyascii`: converts to ASCII using [anyascii](https://github.com/anyascii/anyascii)
1313
- [ ] `asciitable`: prints out an table of ASCII characters
1414
- [ ] `bytecount`: counts the number of occurrences of each byte
15-
- [ ] `ghash`: calculate various hashes
15+
- [x] `certinfo`: print info about an x509 (aka SSL/HTTPS) certificate
16+
- [ ] `ghash`: calculate various [hashes available in the Go standard library](https://pkg.go.dev/crypto#Hash)
1617
- [x] `hexdumpc`: generate canonical hexdump (`hexdump -C`) output in case you don't have [`hexdump`](https://man7.org/linux/man-pages/man1/hexdump.1.html) installed
1718
- [ ] `trilobyte`: translates bytes according to a map
1819
- [ ] `ustrings`: like the standard [`strings`](https://man7.org/linux/man-pages/man1/strings.1.html) utility, but with Unicode support

bin/certdl.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
#
3+
# download a certificate chain from a URL
4+
#
5+
6+
set -o errexit
7+
set -o nounset
8+
set -o pipefail
9+
10+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11+
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
12+
mkdir -p "${REPO_DIR}/tmp"
13+
URL="${1:-www.fileformat.info}"
14+
CERT_FILE="${REPO_DIR}/tmp/cert.pem"
15+
16+
echo "Downloading certificate chain from ${URL} to ${CERT_FILE}"
17+
18+
openssl s_client -showcerts -connect "${URL}:443" </dev/null 2>/dev/null \
19+
| awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/ { print }' > "${CERT_FILE}"

bin/certgen.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
#
3+
# generate a self-signed certificate with OpenSSL for testing certinfo
4+
#
5+
6+
set -o errexit
7+
set -o nounset
8+
set -o pipefail
9+
10+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11+
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
12+
13+
mkdir -p "${REPO_DIR}/tmp"
14+
15+
openssl req \
16+
-x509 \
17+
-newkey rsa:4096 \
18+
-keyout "${REPO_DIR}/tmp/key.pem" \
19+
-out "${REPO_DIR}/tmp/cert.pem" \
20+
-sha256 \
21+
-days 365 \
22+
-nodes \
23+
-subj "/C=US/ST=Pennsylvania/L=Philadelphia/O=ExampleCo/CN=example.com"

cmd/certinfo/certinfo.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"crypto/tls"
6+
"crypto/x509"
7+
"encoding/pem"
8+
"fmt"
9+
"log"
10+
"os"
11+
"strings"
12+
)
13+
14+
func usage() {
15+
fmt.Fprintf(os.Stderr, "Usage: certinfo url|file\n")
16+
fmt.Fprintf(os.Stderr, "Examples:\n")
17+
fmt.Fprintf(os.Stderr, " certinfo https://google.com\n")
18+
fmt.Fprintf(os.Stderr, " certinfo mycert.pem\n")
19+
os.Exit(1)
20+
}
21+
22+
func getHttpsCerts(url string) ([]*x509.Certificate, error) {
23+
24+
conf := &tls.Config{
25+
InsecureSkipVerify: true,
26+
}
27+
28+
host := strings.TrimPrefix(url, "https://")
29+
if idx := strings.Index(host, "/"); idx != -1 {
30+
host = host[:idx]
31+
}
32+
if idx := strings.Index(host, ":"); idx == -1 {
33+
host += ":443" // Default to port 443 for HTTPS
34+
}
35+
36+
conn, err := tls.Dial("tcp", host, conf)
37+
if err != nil {
38+
return nil, fmt.Errorf("error dialing %s: %w", host, err)
39+
}
40+
defer conn.Close()
41+
42+
return conn.ConnectionState().PeerCertificates, nil
43+
}
44+
45+
func main() {
46+
47+
if len(os.Args) < 2 {
48+
usage()
49+
}
50+
51+
var certs []*x509.Certificate
52+
53+
for _, arg := range os.Args[1:] {
54+
if strings.HasPrefix(arg, "https://") {
55+
httpsCerts, httpsErr := getHttpsCerts(arg)
56+
if httpsErr != nil {
57+
log.Fatalf("Error getting HTTPS certificates: %v", httpsErr)
58+
}
59+
certs = httpsCerts
60+
} else {
61+
fileCert, fileErr := os.ReadFile(arg)
62+
if fileErr != nil {
63+
log.Fatalf("Error reading certificate file: %v", fileErr)
64+
}
65+
if bytes.HasPrefix(fileCert, []byte("-----BEGIN ")) {
66+
var block *pem.Block
67+
rest := fileCert
68+
certs = make([]*x509.Certificate, 0)
69+
for {
70+
block, rest = pem.Decode(rest)
71+
if block == nil {
72+
break
73+
}
74+
cert, parseErr := x509.ParseCertificate(block.Bytes)
75+
if parseErr != nil {
76+
log.Fatalf("Error parsing PEM certificate: %v", parseErr)
77+
}
78+
certs = append(certs, cert)
79+
}
80+
} else {
81+
cert, parseErr := x509.ParseCertificate(fileCert)
82+
if parseErr != nil {
83+
log.Fatalf("Error parsing certificate: %v", parseErr)
84+
}
85+
certs = make([]*x509.Certificate, 1)
86+
certs[0] = cert
87+
}
88+
}
89+
90+
fmt.Printf("Certificate Information for %s:\n", arg)
91+
for idx, cert := range certs {
92+
fmt.Printf("\tCertificate %d:\n", idx+1)
93+
fmt.Printf("\t\tIssuer : %s\n", cert.Issuer)
94+
fmt.Printf("\t\tCommon Name : %s \n", cert.Issuer.CommonName)
95+
fmt.Printf("\t\tSubject : %s\n", cert.Subject)
96+
fmt.Printf("\t\tCommon Name : %s \n", cert.Subject.CommonName)
97+
fmt.Printf("\t\tStart : %s \n", cert.NotBefore.Format("2006-01-02"))
98+
fmt.Printf("\t\tExpiry : %s \n", cert.NotAfter.Format("2006-01-02"))
99+
fmt.Printf("\t\tKey Usage : %v \n", cert.KeyUsage)
100+
}
101+
}
102+
}

run.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
#
3+
# run locally
4+
#
5+
6+
set -o errexit
7+
set -o nounset
8+
set -o pipefail
9+
10+
go build -o ./dist/certinfo ./cmd/certinfo
11+
12+
./dist/certinfo https://www.fileformat.info/
13+
./dist/certinfo tmp/cert.pem
14+
./dist/certinfo tmp/cert.der

0 commit comments

Comments
 (0)