Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dc135cfe0 | |||
| 790113e189 |
10
CHANGELOG
10
CHANGELOG
@@ -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
|
||||||
|
|||||||
52
README.md
52
README.md
@@ -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.
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user