cmd: add certser command.
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
CHANGELOG
|
||||
|
||||
v1.13.0 - 2025-11-16
|
||||
|
||||
Add:
|
||||
- cmd/certser: print serial numbers for certificates.
|
||||
- lib/HexEncode: add a new hex encode function handling multiple output
|
||||
formats, including with and without colons.
|
||||
|
||||
v1.12.4 - 2025-11-16
|
||||
|
||||
Changed:
|
||||
|
||||
@@ -2,52 +2,38 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.wntrmute.dev/kyle/goutils/certlib"
|
||||
"git.wntrmute.dev/kyle/goutils/die"
|
||||
"git.wntrmute.dev/kyle/goutils/lib"
|
||||
)
|
||||
|
||||
const (
|
||||
displayInt = iota + 1
|
||||
displayLHex
|
||||
displayUHex
|
||||
)
|
||||
const displayInt lib.HexEncodeMode = iota
|
||||
|
||||
func parseDisplayMode(mode string) int {
|
||||
func parseDisplayMode(mode string) lib.HexEncodeMode {
|
||||
mode = strings.ToLower(mode)
|
||||
switch mode {
|
||||
case "int":
|
||||
|
||||
if mode == "int" {
|
||||
return displayInt
|
||||
case "hex":
|
||||
return displayLHex
|
||||
case "uhex":
|
||||
return displayUHex
|
||||
default:
|
||||
die.With("invalid display mode ", mode)
|
||||
}
|
||||
|
||||
return displayInt
|
||||
return lib.ParseHexEncodeMode(mode)
|
||||
}
|
||||
|
||||
func serialString(cert *x509.Certificate, mode int) string {
|
||||
switch mode {
|
||||
case displayInt:
|
||||
return cert.SerialNumber.String()
|
||||
case displayLHex:
|
||||
return hex.EncodeToString(cert.SerialNumber.Bytes())
|
||||
case displayUHex:
|
||||
return strings.ToUpper(hex.EncodeToString(cert.SerialNumber.Bytes()))
|
||||
default:
|
||||
func serialString(cert *x509.Certificate, mode lib.HexEncodeMode) string {
|
||||
if mode == displayInt {
|
||||
return cert.SerialNumber.String()
|
||||
}
|
||||
|
||||
return lib.HexEncode(cert.SerialNumber.Bytes(), mode)
|
||||
}
|
||||
|
||||
func main() {
|
||||
displayAs := flag.String("d", "int", "display mode (int, hex, uhex)")
|
||||
showExpiry := flag.Bool("e", false, "show expiry date")
|
||||
flag.Parse()
|
||||
|
||||
displayMode := parseDisplayMode(*displayAs)
|
||||
@@ -56,6 +42,10 @@ func main() {
|
||||
cert, err := certlib.LoadCertificate(arg)
|
||||
die.If(err)
|
||||
|
||||
fmt.Printf("%s: %x\n", arg, serialString(cert, displayMode))
|
||||
fmt.Printf("%s: %s", arg, serialString(cert, displayMode))
|
||||
if *showExpiry {
|
||||
fmt.Printf(" (%s)", cert.NotAfter.Format("2006-01-02"))
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
110
lib/lib.go
110
lib/lib.go
@@ -2,9 +2,11 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -109,3 +111,111 @@ func Duration(d time.Duration) string {
|
||||
s += fmt.Sprintf("%dh%s", hours, d)
|
||||
return s
|
||||
}
|
||||
|
||||
type HexEncodeMode uint8
|
||||
|
||||
const (
|
||||
// HexEncodeLower prints the bytes as lowercase hexadecimal.
|
||||
HexEncodeLower HexEncodeMode = iota + 1
|
||||
// HexEncodeUpper prints the bytes as uppercase hexadecimal.
|
||||
HexEncodeUpper
|
||||
// HexEncodeLowerColon prints the bytes as lowercase hexadecimal
|
||||
// with colons between each pair of bytes.
|
||||
HexEncodeLowerColon
|
||||
// HexEncodeUpperColon prints the bytes as uppercase hexadecimal
|
||||
// with colons between each pair of bytes.
|
||||
HexEncodeUpperColon
|
||||
)
|
||||
|
||||
func (m HexEncodeMode) String() string {
|
||||
switch m {
|
||||
case HexEncodeLower:
|
||||
return "lower"
|
||||
case HexEncodeUpper:
|
||||
return "upper"
|
||||
case HexEncodeLowerColon:
|
||||
return "lcolon"
|
||||
case HexEncodeUpperColon:
|
||||
return "ucolon"
|
||||
default:
|
||||
panic("invalid hex encode mode")
|
||||
}
|
||||
}
|
||||
|
||||
func ParseHexEncodeMode(s string) HexEncodeMode {
|
||||
switch strings.ToLower(s) {
|
||||
case "lower":
|
||||
return HexEncodeLower
|
||||
case "upper":
|
||||
return HexEncodeUpper
|
||||
case "lcolon":
|
||||
return HexEncodeLowerColon
|
||||
case "ucolon":
|
||||
return HexEncodeUpperColon
|
||||
}
|
||||
|
||||
panic("invalid hex encode mode")
|
||||
}
|
||||
|
||||
func hexColons(s string) string {
|
||||
if len(s)%2 != 0 {
|
||||
fmt.Fprintf(os.Stderr, "hex string: %s\n", s)
|
||||
fmt.Fprintf(os.Stderr, "hex length: %d\n", len(s))
|
||||
panic("invalid hex string length")
|
||||
}
|
||||
|
||||
n := len(s)
|
||||
if n <= 2 {
|
||||
return s
|
||||
}
|
||||
|
||||
pairCount := n / 2
|
||||
if n%2 != 0 {
|
||||
pairCount++
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.Grow(n + pairCount - 1)
|
||||
|
||||
for i := 0; i < n; i += 2 {
|
||||
b.WriteByte(s[i])
|
||||
|
||||
if i+1 < n {
|
||||
b.WriteByte(s[i+1])
|
||||
}
|
||||
|
||||
if i+2 < n {
|
||||
b.WriteByte(':')
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func hexEncode(b []byte) string {
|
||||
s := hex.EncodeToString(b)
|
||||
|
||||
if len(s)%2 != 0 {
|
||||
s = "0" + s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// HexEncode encodes the given bytes as a hexadecimal string.
|
||||
func HexEncode(b []byte, mode HexEncodeMode) string {
|
||||
str := hexEncode(b)
|
||||
|
||||
switch mode {
|
||||
case HexEncodeLower:
|
||||
return str
|
||||
case HexEncodeUpper:
|
||||
return strings.ToUpper(str)
|
||||
case HexEncodeLowerColon:
|
||||
return hexColons(str)
|
||||
case HexEncodeUpperColon:
|
||||
return strings.ToUpper(hexColons(str))
|
||||
default:
|
||||
panic("invalid hex encode mode")
|
||||
}
|
||||
}
|
||||
|
||||
79
lib/lib_test.go
Normal file
79
lib/lib_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package lib_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.wntrmute.dev/kyle/goutils/lib"
|
||||
)
|
||||
|
||||
func TestHexEncode_LowerUpper(t *testing.T) {
|
||||
b := []byte{0x0f, 0xa1, 0x00, 0xff}
|
||||
|
||||
gotLower := lib.HexEncode(b, lib.HexEncodeLower)
|
||||
if gotLower != "0fa100ff" {
|
||||
t.Fatalf("lib.HexEncode lower: expected %q, got %q", "0fa100ff", gotLower)
|
||||
}
|
||||
|
||||
gotUpper := lib.HexEncode(b, lib.HexEncodeUpper)
|
||||
if gotUpper != "0FA100FF" {
|
||||
t.Fatalf("lib.HexEncode upper: expected %q, got %q", "0FA100FF", gotUpper)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHexEncode_ColonModes(t *testing.T) {
|
||||
// Includes leading zero nibble and a zero byte to verify padding and separators
|
||||
b := []byte{0x0f, 0xa1, 0x00, 0xff}
|
||||
|
||||
gotLColon := lib.HexEncode(b, lib.HexEncodeLowerColon)
|
||||
if gotLColon != "0f:a1:00:ff" {
|
||||
t.Fatalf("lib.HexEncode colon lower: expected %q, got %q", "0f:a1:00:ff", gotLColon)
|
||||
}
|
||||
|
||||
gotUColon := lib.HexEncode(b, lib.HexEncodeUpperColon)
|
||||
if gotUColon != "0F:A1:00:FF" {
|
||||
t.Fatalf("lib.HexEncode colon upper: expected %q, got %q", "0F:A1:00:FF", gotUColon)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHexEncode_EmptyInput(t *testing.T) {
|
||||
var b []byte
|
||||
if got := lib.HexEncode(b, lib.HexEncodeLower); got != "" {
|
||||
t.Fatalf("empty lower: expected empty string, got %q", got)
|
||||
}
|
||||
if got := lib.HexEncode(b, lib.HexEncodeUpper); got != "" {
|
||||
t.Fatalf("empty upper: expected empty string, got %q", got)
|
||||
}
|
||||
if got := lib.HexEncode(b, lib.HexEncodeLowerColon); got != "" {
|
||||
t.Fatalf("empty colon lower: expected empty string, got %q", got)
|
||||
}
|
||||
if got := lib.HexEncode(b, lib.HexEncodeUpperColon); got != "" {
|
||||
t.Fatalf("empty colon upper: expected empty string, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHexEncode_SingleByte(t *testing.T) {
|
||||
b := []byte{0x0f}
|
||||
if got := lib.HexEncode(b, lib.HexEncodeLower); got != "0f" {
|
||||
t.Fatalf("single byte lower: expected %q, got %q", "0f", got)
|
||||
}
|
||||
if got := lib.HexEncode(b, lib.HexEncodeUpper); got != "0F" {
|
||||
t.Fatalf("single byte upper: expected %q, got %q", "0F", got)
|
||||
}
|
||||
// For a single byte, colon modes should not introduce separators
|
||||
if got := lib.HexEncode(b, lib.HexEncodeLowerColon); got != "0f" {
|
||||
t.Fatalf("single byte colon lower: expected %q, got %q", "0f", got)
|
||||
}
|
||||
if got := lib.HexEncode(b, lib.HexEncodeUpperColon); got != "0F" {
|
||||
t.Fatalf("single byte colon upper: expected %q, got %q", "0F", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHexEncode_InvalidModePanics(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatalf("expected panic for invalid mode, but function returned normally")
|
||||
}
|
||||
}()
|
||||
// 0 is not a valid lib.HexEncodeMode (valid modes start at 1)
|
||||
_ = lib.HexEncode([]byte{0x01}, lib.HexEncodeMode(0))
|
||||
}
|
||||
Reference in New Issue
Block a user