Compare commits

...

2 Commits

Author SHA1 Message Date
4dc135cfe0 Update CHANGELOG for v1.11.2. 2025-11-16 13:18:38 -08:00
790113e189 cmd: refactor for code reuse. 2025-11-16 13:15:08 -08:00
5 changed files with 58 additions and 67 deletions

View File

@@ -1,5 +1,15 @@
CHANGELOG CHANGELOG
v1.11.2 - 2025-11-16
Changed
- cmd/ski, cmd/csrpubdump, cmd/tlskeypair: centralize
certificate/private-key/CSR parsing by reusing certlib helpers.
This reduces duplication and improves consistency across commands.
- csr: CSR parsing in the above commands now uses certlib.ParseCSR,
which verifies CSR signatures (behavioral hardening compared to
prior parsing without signature verification).
v1.11.1 - 2025-11-16 v1.11.1 - 2025-11-16
Changed Changed

View File

@@ -2,39 +2,52 @@ GOUTILS
This is a collection of small utility code I've written in Go; the `cmd/` This is a collection of small utility code I've written in Go; the `cmd/`
directory has a number of command-line utilities. Rather than keep all directory has a number of command-line utilities. Rather than keep all
of these in superfluous repositories of their own, or rewriting them of these in superfluous repositories of their own or rewriting them
for each project, I'm putting them here. for each project, I'm putting them here.
The project can be built with the standard Go tooling, or it can be built The project can be built with the standard Go tooling.
with Bazel.
Contents: Contents:
ahash/ Provides hashes from string algorithm specifiers. ahash/ Provides hashes from string algorithm specifiers.
assert/ Error handling, assertion-style. assert/ Error handling, assertion-style.
backoff/ Implementation of an intelligent backoff strategy. backoff/ Implementation of an intelligent backoff strategy.
cache/ Implementations of various caches.
lru/ Least-recently-used cache.
mru/ Most-recently-used cache.
certlib/ Library for working with TLS certificates.
cmd/ cmd/
atping/ Automated TCP ping, meant for putting in cronjobs. atping/ Automated TCP ping, meant for putting in cronjobs.
certchain/ Display the certificate chain from a ca-signed/ Validate whether a certificate is signed by a CA.
TLS connection. cert-bundler/
Create certificate bundles from a source of PEM
certificates.
cert-revcheck/
Check whether a certificate has been revoked or is
expired.
certchain/ Display the certificate chain from a TLS connection.
certdump/ Dump certificate information. certdump/ Dump certificate information.
certexpiry/ Print a list of certificate subjects and expiry times certexpiry/ Print a list of certificate subjects and expiry times
or warn about certificates expiring within a certain or warn about certificates expiring within a certain
window. window.
certverify/ Verify a TLS X.509 certificate, optionally printing certverify/ Verify a TLS X.509 certificate file, optionally printing
the time to expiry and checking for revocations. the time to expiry and checking for revocations.
clustersh/ Run commands or transfer files across multiple clustersh/ Run commands or transfer files across multiple
servers via SSH. servers via SSH.
cruntar/ Untar an archive with hard links, copying instead of cruntar/ (Un)tar an archive with hard links, copying instead of
linking. linking.
csrpubdump/ Dump the public key from an X.509 certificate request. csrpubdump/ Dump the public key from an X.509 certificate request.
data_sync/ Sync the user's homedir to external storage. data_sync/ Sync the user's homedir to external storage.
diskimg/ Write a disk image to a device. diskimg/ Write a disk image to a device.
dumpbytes/ Dump the contents of a file as hex bytes, printing it as
a Go []byte literal.
eig/ EEPROM image generator. eig/ EEPROM image generator.
fragment/ Print a fragment of a file. fragment/ Print a fragment of a file.
host/ Go imlpementation of the host(1) command.
jlp/ JSON linter/prettifier. jlp/ JSON linter/prettifier.
kgz/ Custom gzip compressor / decompressor that handles 99% kgz/ Custom gzip compressor / decompressor that handles 99%
of my use cases. of my use cases.
minmax/ Generate a minmax code for use in uLisp.
parts/ Simple parts database management for my collection of parts/ Simple parts database management for my collection of
electronic components. electronic components.
pem2bin/ Dump the binary body of a PEM-encoded block. pem2bin/ Dump the binary body of a PEM-encoded block.
@@ -44,41 +57,44 @@ Contents:
in a bundle. in a bundle.
renfnv/ Rename a file to base32-encoded 64-bit FNV-1a hash. renfnv/ Rename a file to base32-encoded 64-bit FNV-1a hash.
rhash/ Compute the digest of remote files. rhash/ Compute the digest of remote files.
rolldie/ Roll some dice.
showimp/ List the external (e.g. non-stdlib and outside the showimp/ List the external (e.g. non-stdlib and outside the
current working directory) imports for a Go file. current working directory) imports for a Go file.
ski Display the SKI for PEM-encoded TLS material. ski Display the SKI for PEM-encoded TLS material.
sprox/ Simple TCP proxy. sprox/ Simple TCP proxy.
stealchain/ Dump the verified chain from a TLS stealchain/ Dump the verified chain from a TLS connection to a
connection to a server. server.
stealchain- Dump the verified chain from a TLS stealchain-server/
server/ connection from a client. Dump the verified chain from a TLS connection from
from a client.
subjhash/ Print or match subject info from a certificate. subjhash/ Print or match subject info from a certificate.
tlsinfo/ Print information about a TLS connection (the TLS version
and cipher suite).
tlskeypair/ Check whether a TLS certificate and key file match. tlskeypair/ Check whether a TLS certificate and key file match.
utc/ Convert times to UTC. utc/ Convert times to UTC.
yamll/ A small YAML linter. yamll/ A small YAML linter.
zsearch/ Search for a string in directory of gzipped files.
config/ A simple global configuration system where configuration config/ A simple global configuration system where configuration
data is pulled from a file or an environment variable data is pulled from a file or an environment variable
transparently. transparently.
iniconf/ A simple INI-style configuration system.
dbg/ A debug printer. dbg/ A debug printer.
die/ Death of a program. die/ Death of a program.
fileutil/ Common file functions. fileutil/ Common file functions.
lib/ Commonly-useful functions for writing Go programs. lib/ Commonly-useful functions for writing Go programs.
log/ A syslog library.
logging/ A logging library. logging/ A logging library.
mwc/ MultiwriteCloser implementation. mwc/ MultiwriteCloser implementation.
rand/ Utilities for working with math/rand.
sbuf/ A byte buffer that can be wiped. sbuf/ A byte buffer that can be wiped.
seekbuf/ A read-seekable byte buffer. seekbuf/ A read-seekable byte buffer.
syslog/ Syslog-type logging. syslog/ Syslog-type logging.
tee/ Emulate tee(1)'s functionality in io.Writers. tee/ Emulate tee(1)'s functionality in io.Writers.
testio/ Various I/O utilities useful during testing. testio/ Various I/O utilities useful during testing.
testutil/ Various utility functions useful during testing.
Each program should have a small README in the directory with more Each program should have a small README in the directory with more
information. information.
All code here is licensed under the ISC license. All code here is licensed under the Apache 2.0 license.
Error handling Error handling
-------------- --------------
@@ -99,7 +115,7 @@ Examples:
``` ```
cert, err := certlib.LoadCertificate(path) cert, err := certlib.LoadCertificate(path)
if err != nil { if err != nil {
// sentinel match // sentinel match:
if errors.Is(err, certerr.ErrEmptyCertificate) { if errors.Is(err, certerr.ErrEmptyCertificate) {
// handle empty input // handle empty input
} }
@@ -116,5 +132,3 @@ if err != nil {
} }
} }
``` ```
Avoid including sensitive data (keys, passwords, tokens) in error messages.

View File

@@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"os" "os"
"git.wntrmute.dev/kyle/goutils/certlib"
"git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/die"
) )
@@ -19,14 +20,7 @@ func main() {
in, err := os.ReadFile(fileName) in, err := os.ReadFile(fileName)
die.If(err) die.If(err)
if p, _ := pem.Decode(in); p != nil { csr, _, err := certlib.ParseCSR(in)
if p.Type != "CERTIFICATE REQUEST" {
die.With("INVALID FILE TYPE")
}
in = p.Bytes
}
csr, err := x509.ParseCertificateRequest(in)
die.If(err) die.If(err)
out, err := x509.MarshalPKIXPublicKey(csr.PublicKey) out, err := x509.MarshalPKIXPublicKey(csr.PublicKey)

View File

@@ -2,7 +2,6 @@ package main
import ( import (
"bytes" "bytes"
"crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"crypto/sha1" // #nosec G505 "crypto/sha1" // #nosec G505
@@ -16,6 +15,7 @@ import (
"os" "os"
"strings" "strings"
"git.wntrmute.dev/kyle/goutils/certlib"
"git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/die"
"git.wntrmute.dev/kyle/goutils/lib" "git.wntrmute.dev/kyle/goutils/lib"
) )
@@ -83,28 +83,19 @@ func parse(path string) ([]byte, string, string) {
} }
func parseKey(data []byte) ([]byte, string) { func parseKey(data []byte) ([]byte, string) {
privInterface, err := x509.ParsePKCS8PrivateKey(data) priv, err := certlib.ParsePrivateKeyDER(data)
if err != nil { if err != nil {
privInterface, err = x509.ParsePKCS1PrivateKey(data) die.If(err)
if err != nil {
privInterface, err = x509.ParseECPrivateKey(data)
if err != nil {
die.With("couldn't parse private key.")
}
}
} }
var priv crypto.Signer
var kt string var kt string
switch p := privInterface.(type) { switch priv.Public().(type) {
case *rsa.PrivateKey: case *rsa.PublicKey:
priv = p
kt = keyTypeRSA kt = keyTypeRSA
case *ecdsa.PrivateKey: case *ecdsa.PublicKey:
priv = p
kt = keyTypeECDSA kt = keyTypeECDSA
default: default:
die.With("unknown private key type %T", privInterface) die.With("unknown private key type %T", priv)
} }
public, err := x509.MarshalPKIXPublicKey(priv.Public()) public, err := x509.MarshalPKIXPublicKey(priv.Public())
@@ -134,7 +125,8 @@ func parseCertificate(data []byte) ([]byte, string) {
} }
func parseCSR(data []byte) ([]byte, string) { func parseCSR(data []byte) ([]byte, string) {
csr, err := x509.ParseCertificateRequest(data) // Use certlib to support both PEM and DER and to centralize validation.
csr, _, err := certlib.ParseCSR(data)
die.If(err) die.If(err)
pub := csr.PublicKey pub := csr.PublicKey

View File

@@ -13,6 +13,7 @@ import (
"fmt" "fmt"
"os" "os"
"git.wntrmute.dev/kyle/goutils/certlib"
"git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/die"
) )
@@ -124,34 +125,14 @@ func loadKey(path string) (crypto.Signer, error) {
} }
in = bytes.TrimSpace(in) in = bytes.TrimSpace(in)
p, _ := pem.Decode(in) if p, _ := pem.Decode(in); p != nil {
if p != nil {
if !validPEMs[p.Type] { if !validPEMs[p.Type] {
return nil, errors.New("invalid private key file type " + p.Type) return nil, errors.New("invalid private key file type " + p.Type)
} }
in = p.Bytes return certlib.ParsePrivateKeyPEM(in)
} }
priv, err := x509.ParsePKCS8PrivateKey(in) return certlib.ParsePrivateKeyDER(in)
if err != nil {
priv, err = x509.ParsePKCS1PrivateKey(in)
if err != nil {
priv, err = x509.ParseECPrivateKey(in)
if err != nil {
return nil, err
}
}
}
switch p := priv.(type) {
case *rsa.PrivateKey:
return p, nil
case *ecdsa.PrivateKey:
return p, nil
default:
// should never reach here
return nil, errors.New("invalid private key")
}
} }
func main() { func main() {